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: 23: 24: 25: 26:
27: class CoverallsV1JobsCommand extends Command
28: {
29: 30: 31: 32: 33:
34: protected $rootDir;
35:
36: 37: 38: 39: 40:
41: protected $api;
42:
43: 44: 45: 46: 47:
48: protected $logger;
49:
50:
51:
52: 53: 54: 55: 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: 92: 93: 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);
107: $mem = number_format($event->getMemory() / (1024 * 1024), 2);
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:
114:
115: 116: 117: 118: 119: 120: 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: 139: 140: 141: 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: 158: 159: 160: 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: 183: 184: 185: 186:
187: protected function logCollectedSourceFiles(JsonFile $jsonFile)
188: {
189:
190: $color = function ($coverage, $format) {
191:
192:
193:
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:
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:
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: 226: 227: 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: 240: 241: 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: 254: 255: 256: 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);
266: $this->logger->info(sprintf('File size: <info>%s</info> kB', $filesize));
267:
268: return $this;
269: }
270:
271: 272: 273: 274: 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:
290: if ($response instanceof Response) {
291: $this->logResponse($response);
292: }
293:
294: return;
295: } catch (CurlException $e) {
296:
297:
298:
299:
300:
301: $message = sprintf("Connection error occurred. %s\n\n%s", $e->getMessage(), $e->getTraceAsString());
302: } catch (ClientErrorResponseException $e) {
303:
304: $response = $e->getResponse();
305: $message = sprintf('Client error occurred. status: %s %s', $response->getStatusCode(), $response->getReasonPhrase());
306: } catch (ServerErrorResponseException $e) {
307:
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: }
320:
321: 322: 323: 324: 325: 326: 327: 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:
349:
350: 351: 352: 353: 354:
355: public function setRootDir($rootDir)
356: {
357: $this->rootDir = $rootDir;
358: }
359: }
360: