Overview

Namespaces

  • Contrib
    • Bundle
      • CoverallsBundle
        • Console
        • Entity
      • CoverallsV1Bundle
        • Api
        • Collector
        • Command
        • Config
        • Entity
          • Git
    • Component
      • File
      • Log
      • System
        • Git
  • Guzzle
    • Batch
      • Exception
    • Cache
    • Common
      • Exception
    • Http
      • Curl
      • Exception
      • Message
      • QueryAggregator
    • Inflection
    • Iterator
    • Log
    • Parser
      • Cookie
      • Message
      • UriTemplate
      • Url
    • Plugin
      • Async
      • Backoff
      • Cache
      • Cookie
        • CookieJar
        • Exception
      • CurlAuth
      • ErrorResponse
        • Exception
      • History
      • Log
      • Md5
      • Mock
      • Oauth
    • Service
      • Builder
      • Command
        • Factory
        • LocationVisitor
          • Request
          • Response
      • Description
      • Exception
      • Resource
    • Stream
  • PHP
  • Psr
    • Log
  • Symfony
    • Component
      • Config
        • Definition
          • Builder
          • Exception
        • Exception
        • Loader
        • Resource
        • Util
      • Console
        • Command
        • Formatter
        • Helper
        • Input
        • Output
        • Tester
      • EventDispatcher
        • Debug
      • Finder
        • Adapter
        • Comparator
        • Exception
        • Expression
        • Iterator
        • Shell
      • Stopwatch
      • Yaml
        • Exception

Classes

  • CoverallsV1JobsCommand
  • Overview
  • Namespace
  • Class
  • Tree
  • Todo
  1: <?php
  2: namespace Contrib\Bundle\CoverallsV1Bundle\Command;
  3: 
  4: use Psr\Log\NullLogger;
  5: use Contrib\Component\Log\ConsoleLogger;
  6: use Contrib\Bundle\CoverallsV1Bundle\Api\Jobs;
  7: use Contrib\Bundle\CoverallsV1Bundle\Config\Configurator;
  8: use Contrib\Bundle\CoverallsV1Bundle\Config\Configuration;
  9: use Contrib\Bundle\CoverallsV1Bundle\Entity\JsonFile;
 10: use Guzzle\Http\Client;
 11: use Guzzle\Http\Exception\ClientErrorResponseException;
 12: use Guzzle\Http\Exception\ServerErrorResponseException;
 13: use Guzzle\Http\Exception\CurlException;
 14: use Guzzle\Http\Message\Response;
 15: use Symfony\Component\Console\Command\Command;
 16: use Symfony\Component\Console\Input\InputInterface;
 17: use Symfony\Component\Console\Input\InputOption;
 18: use Symfony\Component\Console\Output\OutputInterface;
 19: use Symfony\Component\Stopwatch\Stopwatch;
 20: 
 21: /**
 22:  * Coveralls Jobs API v1 command.
 23:  *
 24:  * @author Kitamura Satoshi <with.no.parachute@gmail.com>
 25:  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 26:  */
 27: class CoverallsV1JobsCommand extends Command
 28: {
 29:     /**
 30:      * Path to project root directory.
 31:      *
 32:      * @var string
 33:      */
 34:     protected $rootDir;
 35: 
 36:     /**
 37:      * Coveralls Jobs API.
 38:      *
 39:      * @var \Contrib\Bundle\CoverallsV1Bundle\Api\Jobs
 40:      */
 41:     protected $api;
 42: 
 43:     /**
 44:      * Logger.
 45:      *
 46:      * @var \Psr\Log\LoggerInterface
 47:      */
 48:     protected $logger;
 49: 
 50:     // internal method
 51: 
 52:     /**
 53:      * {@inheritdoc}
 54:      *
 55:      * @see \Symfony\Component\Console\Command\Command::configure()
 56:      */
 57:     protected function configure()
 58:     {
 59:         $this
 60:         ->setName('coveralls:v1:jobs')
 61:         ->setDescription('Coveralls Jobs API v1')
 62:         ->addOption(
 63:             'config',
 64:             '-c',
 65:             InputOption::VALUE_OPTIONAL,
 66:             '.coveralls.yml path',
 67:             '.coveralls.yml'
 68:         )
 69:         ->addOption(
 70:             'dry-run',
 71:             null,
 72:             InputOption::VALUE_NONE,
 73:             'Do not send json_file to Jobs API'
 74:         )
 75:         ->addOption(
 76:             'exclude-no-stmt',
 77:             null,
 78:             InputOption::VALUE_NONE,
 79:             'Exclude source files that have no executable statements'
 80:         )
 81:         ->addOption(
 82:             'env',
 83:             '-e',
 84:             InputOption::VALUE_OPTIONAL,
 85:             'Runtime environment name: test, dev, prod',
 86:             'prod'
 87:         );
 88:     }
 89: 
 90:     /**
 91:      * {@inheritdoc}
 92:      *
 93:      * @see \Symfony\Component\Console\Command\Command::execute()
 94:      */
 95:     protected function execute(InputInterface $input, OutputInterface $output)
 96:     {
 97:         $stopwatch = new Stopwatch();
 98:         $stopwatch->start(__CLASS__);
 99: 
100:         $config = $this->loadConfiguration($input, $this->rootDir);
101:         $this->logger = $config->isVerbose() && !$config->isTestEnv() ? new ConsoleLogger($output) : new NullLogger();
102: 
103:         $this->runApi($config);
104: 
105:         $event = $stopwatch->stop(__CLASS__);
106:         $time  = number_format($event->getDuration() / 1000, 3);        // sec
107:         $mem   = number_format($event->getMemory() / (1024 * 1024), 2); // MB
108:         $this->logger->info(sprintf('elapsed time: <info>%s</info> sec memory: <info>%s</info> MB', $time, $mem));
109: 
110:         return 0;
111:     }
112: 
113:     // for Jobs API
114: 
115:     /**
116:      * Load configuration.
117:      *
118:      * @param  InputInterface                                         $input   Input arguments.
119:      * @param  string                                                 $rootDir Path to project root directory.
120:      * @return \Contrib\Bundle\CoverallsV1Bundle\Config\Configuration
121:      */
122:     protected function loadConfiguration(InputInterface $input, $rootDir)
123:     {
124:         $coverallsYmlPath = $input->getOption('config');
125: 
126:         $ymlPath      = $this->rootDir . DIRECTORY_SEPARATOR . $coverallsYmlPath;
127:         $configurator = new Configurator();
128: 
129:         return $configurator
130:         ->load($ymlPath, $rootDir)
131:         ->setDryRun($input->getOption('dry-run'))
132:         ->setExcludeNoStatementsUnlessFalse($input->getOption('exclude-no-stmt'))
133:         ->setVerbose($input->getOption('verbose'))
134:         ->setEnv($input->getOption('env'));
135:     }
136: 
137:     /**
138:      * Run Jobs API.
139:      *
140:      * @param  Configuration $config Configuration.
141:      * @return void
142:      */
143:     protected function runApi(Configuration $config)
144:     {
145:         $client    = new Client();
146:         $this->api = new Jobs($config, $client);
147: 
148:         $this
149:         ->collectCloverXml($config)
150:         ->collectGitInfo()
151:         ->collectEnvVars()
152:         ->dumpJsonFile($config)
153:         ->send();
154:     }
155: 
156:     /**
157:      * Collect clover XML into json_file.
158:      *
159:      * @param  Configuration                                                    $config Configuration.
160:      * @return \Contrib\Bundle\CoverallsV1Bundle\Command\CoverallsV1JobsCommand
161:      */
162:     protected function collectCloverXml(Configuration $config)
163:     {
164:         $this->logger->info('Load coverage clover log:');
165: 
166:         foreach ($config->getCloverXmlPaths() as $path) {
167:             $this->logger->info(sprintf('  - %s', $path));
168:         }
169: 
170:         $this->api->collectCloverXml();
171: 
172:         $jsonFile = $this->api->getJsonFile();
173: 
174:         if ($jsonFile->hasSourceFiles()) {
175:             $this->logCollectedSourceFiles($jsonFile);
176:         }
177: 
178:         return $this;
179:     }
180: 
181:     /**
182:      * Log collected source files.
183:      *
184:      * @param  JsonFile $jsonFile
185:      * @return void
186:      */
187:     protected function logCollectedSourceFiles(JsonFile $jsonFile)
188:     {
189:         // @codeCoverageIgnoreStart
190:         $color = function ($coverage, $format) {
191:             // green  90% - 100% <info>
192:             // yellow 80% -  90% <comment>
193:             // red     0% -  80% <fg=red>
194:             if ($coverage >= 90) {
195:                 return sprintf('<info>%s</info>', $format);
196:             } elseif ($coverage >= 80) {
197:                 return sprintf('<comment>%s</comment>', $format);
198:             } else {
199:                 return sprintf('<fg=red>%s</fg=red>', $format);
200:             }
201:         };
202:         // @codeCoverageIgnoreEnd
203: 
204:         $sourceFiles = $jsonFile->getSourceFiles();
205:         $numFiles    = count($sourceFiles);
206: 
207:         $this->logger->info(sprintf('Found <info>%s</info> source file%s:', number_format($numFiles), $numFiles > 1 ? 's' : ''));
208: 
209:         foreach ($sourceFiles as $sourceFile) {
210:             /* @var $sourceFile \Contrib\Bundle\CoverallsV1Bundle\Entity\SourceFile */
211:             $coverage = $sourceFile->reportLineCoverage();
212:             $template = '  - ' . $color($coverage, '%6.2f%%') . ' %s';
213: 
214:             $this->logger->info(sprintf($template, $coverage, $sourceFile->getName()));
215:         }
216: 
217:         $coverage = $jsonFile->reportLineCoverage();
218:         $template = 'Coverage: ' . $color($coverage, '%6.2f%% (%d/%d)');
219:         $metrics  = $jsonFile->getMetrics();
220: 
221:         $this->logger->info(sprintf($template, $coverage, $metrics->getCoveredStatements(), $metrics->getStatements()));
222:     }
223: 
224:     /**
225:      * Collect git repository info into json_file.
226:      *
227:      * @return \Contrib\Bundle\CoverallsV1Bundle\Command\CoverallsV1JobsCommand
228:      */
229:     protected function collectGitInfo()
230:     {
231:         $this->logger->info('Collect git info');
232: 
233:         $this->api->collectGitInfo();
234: 
235:         return $this;
236:     }
237: 
238:     /**
239:      * Collect environment variables.
240:      *
241:      * @return \Contrib\Bundle\CoverallsV1Bundle\Command\CoverallsV1JobsCommand
242:      */
243:     protected function collectEnvVars()
244:     {
245:         $this->logger->info('Read environment variables');
246: 
247:         $this->api->collectEnvVars($_SERVER);
248: 
249:         return $this;
250:     }
251: 
252:     /**
253:      * Dump uploading json file.
254:      *
255:      * @param  Configuration                                                    $config Configuration.
256:      * @return \Contrib\Bundle\CoverallsV1Bundle\Command\CoverallsV1JobsCommand
257:      */
258:     protected function dumpJsonFile(Configuration $config)
259:     {
260:         $jsonPath = $config->getJsonPath();
261:         $this->logger->info(sprintf('Dump uploading json file: %s', $jsonPath));
262: 
263:         $this->api->dumpJsonFile();
264: 
265:         $filesize = number_format(filesize($jsonPath) / 1024, 2); // kB
266:         $this->logger->info(sprintf('File size: <info>%s</info> kB', $filesize));
267: 
268:         return $this;
269:     }
270: 
271:     /**
272:      * Send json_file to jobs API.
273:      *
274:      * @return void
275:      */
276:     protected function send()
277:     {
278:         $this->logger->info(sprintf('Submitting to %s', Jobs::URL));
279: 
280:         try {
281:             $response = $this->api->send();
282: 
283:             $message = $response
284:                 ? sprintf('Finish submitting. status: %s %s', $response->getStatusCode(), $response->getReasonPhrase())
285:                 : 'Finish dry run';
286: 
287:             $this->logger->info($message);
288: 
289:             // @codeCoverageIgnoreStart
290:             if ($response instanceof Response) {
291:                 $this->logResponse($response);
292:             }
293: 
294:             return;
295:         } catch (CurlException $e) {
296:             // connection error
297:             // tested with network disconnected and got message:
298:             //   Connection error occurred.
299:             //   [curl] 6: Could not resolve host:
300:             //   (nil); nodename nor servname provided, or not known [url] https://coveralls.io/api/v1/jobs
301:             $message  = sprintf("Connection error occurred. %s\n\n%s", $e->getMessage(), $e->getTraceAsString());
302:         } catch (ClientErrorResponseException $e) {
303:             // 422 Unprocessable Entity
304:             $response = $e->getResponse();
305:             $message  = sprintf('Client error occurred. status: %s %s', $response->getStatusCode(), $response->getReasonPhrase());
306:         } catch (ServerErrorResponseException $e) {
307:             // 503 Service Unavailable
308:             $response = $e->getResponse();
309:             $message  = sprintf('Server error occurred. status: %s %s', $response->getStatusCode(), $response->getReasonPhrase());
310:         } catch (\Exception $e) {
311:             $message  = sprintf("%s\n\n%s", $e->getMessage(), $e->getTraceAsString());
312:         }
313: 
314:         $this->logger->error($message);
315: 
316:         if (isset($response)) {
317:             $this->logResponse($response);
318:         }
319:     } // @codeCoverageIgnoreEnd
320: 
321:     /**
322:      * Log response.
323:      *
324:      * @param  Response $response API response.
325:      * @return void
326:      *
327:      * @codeCoverageIgnore
328:      */
329:     protected function logResponse(Response $response)
330:     {
331:         $body = $response->json();
332: 
333:         if (isset($body['error'])) {
334:             if (isset($body['message'])) {
335:                 $this->logger->error($body['message']);
336:             }
337:         } else {
338:             if (isset($body['message'])) {
339:                 $this->logger->info(sprintf('Accepted %s', $body['message']));
340:             }
341: 
342:             if (isset($body['url'])) {
343:                 $this->logger->info(sprintf('You can see the build on %s', $body['url']));
344:             }
345:         }
346:     }
347: 
348:     // accessor
349: 
350:     /**
351:      * Set root directory.
352:      *
353:      * @param string $rootDir Path to project root directory.
354:      */
355:     public function setRootDir($rootDir)
356:     {
357:         $this->rootDir = $rootDir;
358:     }
359: }
360: 
php-coveralls API documentation generated by ApiGen 2.8.0