1
+ <?php
2
+
3
+ declare (strict_types=1 );
4
+
5
+ namespace CoveragePathFixer \Command ;
6
+
7
+ use RecursiveDirectoryIterator ;
8
+ use RecursiveIteratorIterator ;
9
+ use RecursiveRegexIterator ;
10
+ use RegexIterator ;
11
+ use SebastianBergmann \CodeCoverage \CodeCoverage ;
12
+ use SebastianBergmann \CodeCoverage \Filter ;
13
+ use SebastianBergmann \CodeCoverage \Report \Clover ;
14
+ use SebastianBergmann \CodeCoverage \Report \PHP ;
15
+ use Symfony \Component \Console \Command \Command ;
16
+ use Symfony \Component \Console \Input \InputArgument ;
17
+ use Symfony \Component \Console \Input \InputInterface ;
18
+ use Symfony \Component \Console \Input \InputOption ;
19
+ use Symfony \Component \Console \Output \OutputInterface ;
20
+
21
+ class ChangePathPrefix extends Command
22
+ {
23
+ protected static $ defaultName = 'fix ' ;
24
+
25
+ protected function configure (): void
26
+ {
27
+ $ this ->setDescription ('Swaps a given path prefix with another in your coverage files ' )
28
+ ->setHelp (
29
+ 'This command will recursively search for coverage (.cov) files and alter the code ' .
30
+ 'paths held within such that the files can be found. \n\n ' .
31
+ 'e.g. "/app/src" can be swapped with e.g. "/home/ci/src" '
32
+ )
33
+
34
+ ->addArgument (
35
+ 'directory_to_search ' ,
36
+ InputArgument::REQUIRED ,
37
+ 'The directory to recursively search for ".cov" files ' ,
38
+ )
39
+
40
+ ->addArgument (
41
+ 'original_prefix ' ,
42
+ InputArgument::REQUIRED ,
43
+ 'The orginal path prefix e.g. "/app/src" '
44
+ )
45
+
46
+ ->addArgument (
47
+ 'replacement_prefix ' ,
48
+ InputArgument::REQUIRED ,
49
+ 'The new path prefix e.g. "/home/ci/src" '
50
+ )
51
+
52
+ ->addOption (
53
+ 'merge ' ,
54
+ 'm ' ,
55
+ InputOption::VALUE_REQUIRED ,
56
+ 'Merge the discovered files into the specified single output coverage (.cov) file '
57
+ )
58
+
59
+ ->addOption (
60
+ 'clover ' ,
61
+ 'c ' ,
62
+ InputOption::VALUE_NONE ,
63
+ 'Output an additional clover format coverage file alongside each .cov file that is processed '
64
+ );
65
+ }
66
+
67
+ protected function execute (InputInterface $ input , OutputInterface $ output ): int
68
+ {
69
+ try {
70
+ $ files = $ this ->findCoverageFiles ($ input ->getArgument ('directory_to_search ' ));
71
+
72
+ $ files = $ this ->iterateCoverageFiles (
73
+ $ files ,
74
+ $ input ->getArgument ('original_prefix ' ),
75
+ $ input ->getArgument ('replacement_prefix ' )
76
+ );
77
+
78
+ if ($ path = $ input ->getOption ('merge ' )) {
79
+ $ files = $ this ->mergeCoverageFiles ($ files , $ path );
80
+ }
81
+
82
+ $ this ->outputCoverageFiles ($ files , $ input ->getOption ('clover ' ));
83
+ } catch (\Exception $ ex ) {
84
+ return $ ex ->getCode ();
85
+ }
86
+
87
+ return 0 ;
88
+ }
89
+
90
+ private function changePrefix (array $ data , string $ originalPrefix , string $ replacementPrefix ): array
91
+ {
92
+ return array_combine (array_map (function ($ el ) use ($ originalPrefix , $ replacementPrefix ) {
93
+ $ el = preg_replace ('#^ ' . $ originalPrefix . '# ' , $ replacementPrefix , $ el );
94
+ return $ el ;
95
+ }, array_keys ($ data )), array_values ($ data ));
96
+ }
97
+
98
+ private function iterateCoverageFiles (array $ files , string $ originalPrefix , string $ replacementPrefix ): array
99
+ {
100
+ return array_map (function (array $ file ) use ($ originalPrefix , $ replacementPrefix ) {
101
+ $ coverage = $ this ->loadCoverageFile ($ file [0 ]);
102
+
103
+ $ data = $ this ->changePrefix ($ coverage ->getData (), $ originalPrefix , $ replacementPrefix );
104
+ $ whiteList = $ this ->changePrefix (
105
+ $ coverage ->filter ()->getWhitelistedFiles (),
106
+ $ originalPrefix ,
107
+ $ replacementPrefix
108
+ );
109
+
110
+ $ filter = new Filter ();
111
+ $ filter ->setWhitelistedFiles ($ whiteList );
112
+
113
+ $ coverage = new CodeCoverage (null , $ filter );
114
+ $ coverage ->setData ($ data );
115
+
116
+ return $ coverage ;
117
+ }, $ files );
118
+ }
119
+
120
+ private function findCoverageFiles (string $ directory ): array
121
+ {
122
+ $ path = realpath ($ directory );
123
+
124
+ $ directory = new RecursiveDirectoryIterator ($ path );
125
+ $ iterator = new RecursiveIteratorIterator ($ directory );
126
+ $ filtered = new RegexIterator ($ iterator , '/^.+\.cov$/i ' , RecursiveRegexIterator::GET_MATCH );
127
+
128
+ return iterator_to_array ($ filtered );
129
+ }
130
+
131
+ private function loadCoverageFile (string $ file ): CodeCoverage
132
+ {
133
+ $ coverage = include $ file ;
134
+
135
+ if (!($ coverage instanceof CodeCoverage)) {
136
+ unset($ coverage );
137
+ throw new \Exception ('File with coverage extension not resolved to CodeCoverage class ' );
138
+ }
139
+
140
+ return $ coverage ;
141
+ }
142
+
143
+ private function mergeCoverageFiles (array $ files , string $ path ): array
144
+ {
145
+ $ coverage = new CodeCoverage ();
146
+
147
+ foreach ($ files as $ file => $ coverage ) {
148
+ $ coverage ->merge ($ coverage );
149
+ }
150
+
151
+ return [$ path => $ coverage ];
152
+ }
153
+
154
+ private function outputCoverageFiles (array $ files , bool $ asClover = false ): void
155
+ {
156
+ array_walk ($ files , function ($ coverage , $ path ) use ($ asClover ) {
157
+ if ($ asClover ) {
158
+ $ filename = basename ($ path , '.cov ' );
159
+ $ directory = dirname ($ path );
160
+
161
+ $ reportWriter = new Clover ();
162
+ $ reportWriter ->process ($ coverage , $ directory . DIRECTORY_SEPARATOR . $ filename . '.xml ' );
163
+ }
164
+
165
+ $ reportWriter = new PHP ();
166
+ $ reportWriter ->process ($ coverage , $ path );
167
+ });
168
+ }
169
+ }
0 commit comments