3
3
4
4
namespace ChinthakaGodawita \CodeceptionTimekeeper ;
5
5
6
- use Codeception \Test \Descriptor as TestDescriptor ;
7
6
use Codeception \Test \Loader ;
8
- use Codeception \TestInterface ;
9
7
use Generator ;
10
8
use PHPUnit \Framework \DataProviderTestSuite ;
11
- use PHPUnit \Framework \SelfDescribing ;
12
9
use Robo \Exception \TaskException ;
13
10
use Robo \Result ;
14
11
use Robo \Task \BaseTask ;
@@ -33,7 +30,7 @@ class TimeSplitterTask extends BaseTask
33
30
/**
34
31
* @var string The filename pattern under which groups will be saved.
35
32
*/
36
- private $ groupOutputLoc = 'tests/ _data/timekeeper/group_ ' ;
33
+ private $ groupOutputLoc = '_data/timekeeper/group_ ' ;
37
34
38
35
/**
39
36
* @var string The directory that holds test files.
@@ -71,7 +68,16 @@ public function run(): Result
71
68
$ tests = $ testLoader ->getTests ();
72
69
$ testCount = count ($ tests );
73
70
74
- $ this ->printTaskInfo ("Splitting {$ testCount } into {$ this ->groupCount } groups of equal runtime... " );
71
+ if ($ this ->groupCount > $ testCount ) {
72
+ return Result::error (
73
+ $ this ,
74
+ "Provided group count ( {$ this ->groupCount }) is more than the number of tests ( {$ testCount })! "
75
+ );
76
+ }
77
+
78
+ $ this ->printTaskInfo (
79
+ "Splitting {$ testCount } tests into {$ this ->groupCount } groups of equal runtime... "
80
+ );
75
81
76
82
$ timeReport = null ;
77
83
try {
@@ -107,9 +113,14 @@ public function run(): Result
107
113
}
108
114
}
109
115
110
- foreach ($ groups as $ idx => $ tests ) {
111
- $ fileName = $ this ->groupOutputLoc . $ idx ;
112
- $ this ->printTaskInfo ("Writing group {$ idx } to: $ fileName " );
116
+ $ groupIdx = null ;
117
+ foreach ($ groups as $ groupIdx => $ tests ) {
118
+ if (\count ($ tests ) === 0 ) {
119
+ break ;
120
+ }
121
+
122
+ $ fileName = $ this ->groupOutputLoc . $ groupIdx ;
123
+ $ this ->printTaskInfo ("Writing group {$ groupIdx } to: $ fileName " );
113
124
$ success = file_put_contents ($ fileName , implode ("\n" , $ tests ));
114
125
if (!$ success ) {
115
126
throw new TaskException (
@@ -119,27 +130,73 @@ public function run(): Result
119
130
}
120
131
}
121
132
122
- return Result::success ($ this , "{$ this ->groupCount } test groups created " );
133
+ if ($ groupIdx === null ) {
134
+ $ this ->printTaskWarning ('No test groups were created ' );
135
+ } else {
136
+ $ groupCount = $ groupIdx + 1 ;
137
+ $ this ->printTaskInfo ("{$ groupCount } test groups created " );
138
+ }
139
+
140
+ return Result::success ($ this );
123
141
}
124
142
143
+ /**
144
+ * Splits tests into equal groups, ignoring test runtimes.
145
+ *
146
+ * @param Generator|Test[] $tests
147
+ *
148
+ * @return array
149
+ */
125
150
private function splitTestsByGroup (Generator $ tests ): array
126
151
{
127
- // @TODO.
128
- return [];
152
+ $ groups = array_fill (0 , $ this ->groupCount , []);
153
+ $ skippedTests = [];
154
+
155
+ $ groupIdx = 0 ;
156
+ foreach ($ tests as $ test ) {
157
+ $ testPath = $ test ->path ();
158
+
159
+ if ($ test ->isSkipped ()) {
160
+ $ skippedTests [] = $ testPath ;
161
+ } else {
162
+ $ groups [$ groupIdx ][] = $ testPath ;
163
+ }
164
+
165
+ $ groupIdx ++;
166
+ if ($ groupIdx === $ this ->groupCount ) {
167
+ $ groupIdx = 0 ;
168
+ }
169
+ }
170
+
171
+ // Add skipped tests onto the last group as they take relatively no time
172
+ // to run.
173
+ $ maxGroupIdx = $ this ->groupCount - 1 ;
174
+ if (count ($ skippedTests ) > 0 ) {
175
+ $ groups [$ maxGroupIdx ] = array_merge ($ groups [$ maxGroupIdx ], $ skippedTests );
176
+ }
177
+
178
+ return $ groups ;
129
179
}
130
180
181
+ /**
182
+ * Splits tests into groups of _roughly_ equal runtimes.
183
+ *
184
+ * @param Generator|Test[] $tests
185
+ * @param TimeReport $timeReport
186
+ *
187
+ * @return array
188
+ */
131
189
private function splitTestsByRuntime (Generator $ tests , TimeReport $ timeReport ): array
132
190
{
133
191
$ skippedTests = [];
134
192
$ testsWithRuntime = [];
135
193
$ testsWithoutRuntime = [];
136
194
137
195
foreach ($ tests as $ test ) {
138
- $ testMeta = $ test ->getMetadata ();
139
- $ testPath = $ this ->getTestRelativePath ($ test );
196
+ $ testPath = $ test ->path ();
140
197
$ runtime = $ timeReport ->getTime ($ testPath );
141
198
142
- if ($ testMeta -> getSkip () !== null ) {
199
+ if ($ test -> isSkipped () ) {
143
200
$ skippedTests [] = $ testPath ;
144
201
} elseif ($ runtime === null ) {
145
202
$ testsWithoutRuntime [] = $ testPath ;
@@ -155,18 +212,10 @@ private function splitTestsByRuntime(Generator $tests, TimeReport $timeReport):
155
212
$ sums = array_fill (0 , $ this ->groupCount , 0 );
156
213
$ groups = array_fill (0 , $ this ->groupCount , []);
157
214
158
- $ maxLoops = 3 * $ this ->groupCount ;
159
215
foreach ($ testsWithRuntime as $ testPath => $ runtime ) {
160
216
$ idx = 0 ;
161
217
$ loops = 0 ;
162
218
while (true ) {
163
- if ($ loops > $ maxLoops ) {
164
- throw new TaskException (
165
- $ this ,
166
- "Max loop count ( {$ maxLoops }) reached. "
167
- );
168
- }
169
-
170
219
$ sum = $ sums [$ idx ];
171
220
$ prevIdx = $ idx - 1 ;
172
221
if ($ prevIdx < 0 ) {
@@ -176,13 +225,12 @@ private function splitTestsByRuntime(Generator $tests, TimeReport $timeReport):
176
225
if ($ nextIdx === $ this ->groupCount ) {
177
226
$ nextIdx = 0 ;
178
227
}
179
- if ($ sum === 0 || ($ sum < $ sums [$ prevIdx ] && $ sum < $ sums [$ nextIdx ])) {
228
+ if ($ sum === 0 || (( $ sum < $ sums [$ prevIdx ] && $ sum < $ sums [$ nextIdx ]) )) {
180
229
$ sums [$ idx ] += $ runtime ;
181
230
$ groups [$ idx ][] = $ testPath ;
182
231
break ;
183
232
}
184
233
$ idx ++;
185
- $ loops ++;
186
234
}
187
235
}
188
236
@@ -210,7 +258,7 @@ private function splitTestsByRuntime(Generator $tests, TimeReport $timeReport):
210
258
/**
211
259
* @param array $tests
212
260
*
213
- * @return Generator|TestInterface []
261
+ * @return Generator|Test []
214
262
*/
215
263
private function testIterator (array $ tests ): Generator
216
264
{
@@ -219,29 +267,8 @@ private function testIterator(array $tests): Generator
219
267
$ test = current ($ test ->tests ());
220
268
}
221
269
222
- yield $ test ;
270
+ yield new Test ( $ test) ;
223
271
}
224
272
}
225
273
226
- /**
227
- * Get the path to a particular test, relative to the Robofile.
228
- *
229
- * @param SelfDescribing $test
230
- *
231
- * @return string
232
- */
233
- private function getTestRelativePath (SelfDescribing $ test ): string
234
- {
235
- $ path = DIRECTORY_SEPARATOR . TestDescriptor::getTestFullName ($ test );
236
- // Robo updates PHP's current working directory to the location of the
237
- // Robofile.
238
- $ currentDir = getcwd () . DIRECTORY_SEPARATOR ;
239
-
240
- if (strpos ($ path , $ currentDir ) === 0 ) {
241
- $ path = substr ($ path , strlen ($ currentDir ));
242
- }
243
-
244
- return $ path ;
245
- }
246
-
247
274
}
0 commit comments