Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breakfast: rerun defects first #3147

Closed
wants to merge 1 commit into from
Closed

Breakfast: rerun defects first #3147

wants to merge 1 commit into from

Conversation

epdenouden
Copy link
Contributor

@epdenouden epdenouden commented May 28, 2018

Use case

Rerun defective unit tests first to speed up the red-green-refactor cycle!

Test results and timings are shared between tests runs using a cache. This allows for sorting defective tests to the front of the execution order using an additional sorter. This new sort option is compatible with existing test ordering options.

This project is a followup to my previous work on reordering test execution. The code is available in my Breakfast development branch.

Summary of changes

  • the TestSuiteSorter gains the ability to sort suites and tests by priority of defects and execution time
  • added a ResultCacheExtension to gather result and timing information during test runs
  • added a TestResultCache for sharing of result state between runs
  • added caching of run results using CLI flag --cache-result and configuration attribute cacheResult='true'
  • added sorting defects to run as quick as possible with CLI flag --order-by=defects and configuration attribute executionOrder='defects'
  • ability to specifiy the cache filename using CLI flag --cache-result-file and configuration attribute cacheResultFile
  • more extensive testing of sorting scenarios

How to test

  1. To activate storing the results of a run use --cache-result:

    phpunit --cache-result
    
  2. For this example we switch the cache on for every run by adding the attribute cacheResult="true" to the <phpunit> element in phpunit.xml.

    <phpunit cacheResult="true"> <-- no other changes required --></phpunit>
  3. Break TestCaseTest by running it backwards:

    phpunit --order-by=reverse tests/Framework/TestCaseTest.php
    
  4. Look at the cache file. By default this is .phpunit.result.cache in the PHPUnit working directory.

  5. Break TestCaseTest again with the sorting feature turning on:

    phpunit --order-by=defects tests/Framework/TestCaseTest.php
    
  6. The skipped tests are still there but are run immediately now, but still fail. This is easily avoided by asking the sorter to keep dependencies in mind:

    phpunit --order-by=depends,defects tests/Framework/TestCaseTest.php
    

Design considerations

Like the New Order feature the aim is for a robust system with sane defaults that silently does its work and is easily maintained by other developers:

  • Require as little new configuration as possible. Just add cacheResult="true" to your configuration and it silently does its work.
  • Add no extra output as PHPUnit often lives in automated pipelines and dev scripts.
  • Provide robust and maintainable code. This feature also provides a lot of new test coverage for functionality from Add functionality to test dependencies by changing test running order #3092.
  • Implemented a simplistic cache on purpose. Obviously people might want more complex caches based on users, groups, configuration, git hash, etc etc. Let's see what people need.

@codecov-io
Copy link

codecov-io commented May 28, 2018

Codecov Report

Merging #3147 into master will increase coverage by 0.59%.
The diff coverage is 98.24%.

Impacted file tree graph

@@             Coverage Diff             @@
##             master   #3147      +/-   ##
===========================================
+ Coverage      81.5%   82.1%   +0.59%     
- Complexity     3412    3506      +94     
===========================================
  Files           137     140       +3     
  Lines          9014    9218     +204     
===========================================
+ Hits           7347    7568     +221     
+ Misses         1667    1650      -17
Impacted Files Coverage Δ Complexity Δ
src/Util/Configuration.php 97.11% <100%> (+0.68%) 179 <0> (+7) ⬆️
src/Runner/ResultCacheExtension.php 100% <100%> (ø) 13 <13> (?)
src/Util/NullTestResultCache.php 100% <100%> (ø) 4 <4> (?)
src/Runner/TestSuiteSorter.php 100% <100%> (+6.12%) 43 <28> (+14) ⬆️
src/TextUI/Command.php 71.5% <100%> (+3.22%) 204 <7> (+11) ⬆️
src/Util/TestResultCache.php 100% <100%> (ø) 25 <25> (?)
src/Framework/TestResult.php 72.75% <100%> (+0.65%) 163 <1> (+5) ⬆️
src/TextUI/TestRunner.php 63.96% <84.61%> (+1.56%) 274 <0> (+15) ⬆️
... and 5 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1bde207...7dc6729. Read the comment docs.

@sebastianbergmann
Copy link
Owner

  • This branch cannot be rebased due to conflicts.
  • Why is the ResultCacheListener needed, or rather why is it needed to be known to the user? As this pull request is about adding functionality to the "core" of PHPUnit, there is no need to expose the usage of an extension point like that. We can simply attach the listener when we need it.
  • I think we should add a CLI switch and configuration option to record test results instead.
  • I would prefer not to use the old, bloated TestListener interface for this but rather the new hook interfaces (introduced in PHPUnit 7.1 with Towards a better plugin infrastructure #3002), if possible.

@sebastianbergmann sebastianbergmann added the type/enhancement A new idea that should be implemented label May 29, 2018
@sebastianbergmann sebastianbergmann added this to the PHPUnit 7.3 milestone May 29, 2018
@epdenouden
Copy link
Contributor Author

Thanks for the quick review, again!

  • Branch is fixed. The culprit was a backported TestSuiteSorterTest. Doing a git rebase instead of merge even suggested just removing a conflicting commit altogether.
  • I extended the public TestListener based on the example in the manual. I would also prefer to keep this internal. Will refactor into an extension that can be switched on with a command line flag, also makes testing cleaner.

/**
* @var string
*/
public const DEFAULT_RESULT_CACHE_FILENAME = 'result_cache.json';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I have my project, eg ~/php-cs-fixer folder, and I would run phpunit with cache enabled, it would create ~/php-cs-fixer/result_cache.json ? if so, please let us make it phpunit.cache.json or sth like this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no opinion on the filename of the default value. As a developer I want the following:

  1. Simple usage that just works in a basic code, test, repeat cycle. A file in the working directory works well.
  2. PHPUnit is part of automation: use the environment variable to do your devops and have it store the cache(s) for users/configs/build-unique-ID/git-hashes/etc however en whereever you want.

The default implementation of TestResultCache is there so the simplest use cases work out-of-the-box. The current cache is just a simple MVP. I would prefer NOT to use an external dependency like SQLite like PHPUnit Clever and Smart did, but I am starting to see why they went that way.

Copy link
Contributor

@keradus keradus May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, here I'm even not talking about using SQLite or anything like that. (while Indeed, i can see value of it - yet in caching of PHP CS Fixer we don't use it as well - too fancy for our simple purpose).

what I'm saying here @epdenouden is that if I will run phpunit in my project, using cache and out-of-the-box approach (so I don't configure anything extra), phpunit will leave result_cache.json file there. if phpunit will leave sth, it shall indicate it's his leftover, putting it to the name part.
like

-result_cache
+phpunit_cache

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keradus quick reply: that was a good suggestion, thx! Have pushed a change for that.

I mixed two related topics regarding the caching, making my comment confusion. Adding in persistence cleanly has been more of a chore than I expected. Will spend more time tonight on a cleaner approach.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new name looks shiny, thanks for the change!
then, I will try to review changes tomorrow ;)

return;
}

$this->defects = $cacheData['defects'];
Copy link
Contributor

@keradus keradus May 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have no clue that decoded data contain such key...
and even if - what is the content of it.

it's optimistic approach here.

cache file could be treated as-is, one is not supposed to modify it manually.
maybe serialize and then unserialize with unserialize(..., ['allowed_classes' => CacheData::class]) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the earliest versions of Breakfast were just that: patches to the code that quickly (un)serialized a results file to store the state between runs. I'll try some other options.


private function formatJSONOutputStruct(): string
{
return \json_encode([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json_encode can fail as well (eg on non utf-8 input)

/**
* @var array
*/
private $times = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the content of those collections? array of.... ints? is key meaningful or numeric?
how single defect looks like ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, all good points regarding the results cache. Started refactoring that part to make it more robust and cleaner.

phpunit.xsd Outdated
@@ -227,6 +233,7 @@
<xs:attribute name="printerClass" type="xs:string" default="PHPUnit\TextUI\ResultPrinter"/>
<xs:attribute name="printerFile" type="xs:anyURI"/>
<xs:attribute name="processIsolation" type="xs:boolean" default="false"/>
<xs:attribute name="stopOnDefect" type="xs:boolean" default="false"/>
Copy link
Contributor

@keradus keradus May 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to document somewhere what is a defect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. I have added a description to the help message.


private function updateRunState(\PHPUnit\Framework\Test $test, string $state): void
{
$testName = $this->fullTestName($test);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too many spaces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

];

/**
* @var array
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array of ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added description

/**
* @var array
*/
private $defectSortWeight = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, was a precalculated array before.

}
}

private function sort(TestSuite $suite, int $order, bool $resolveDependencies): void
public function setCache(TestResultCache $cache): void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this property shall not be exposed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the method is only used in the test. Will clean it up :)

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;

class TestSuiteSorterEmptyTestCaseTest extends TestCase
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no TestSuiteSorterEmptyTestCase class, how we can have test for it ?

test here shall be part of TestSuiteSorterTest test class, it's one concrete, edge scenario to be tested, not completely other class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored the fixtures in TestSuiteSorterTest and added this test in. Cleaner, thanks for the comment!

It started off as a quick test where I didn't want the fixtures from TestSuiteSorterTest. The name is indeed confusing, it was supposed to mean "TestSuiteSorter (tested with a) EmptyTestCase".

$sorter = new TestSuiteSorter();
$sorter->setCache($cache);
$_sorter = new \ReflectionClass($sorter);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_? leading _?

what about $sorterReflection ?

also, having a need to access class' internals in test indicate bad class structure. maybe Cache shall be injected to Sorter via constructor ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $_var as a temporary version of $var as I quickly needed one, like the $_suite in TestRunner. Will pick something else for now, then look at the use of Reflection.

I can imagine $sorter = new TestSuiteSorter($cache) but to me that feels like juggling the hot potato of persisting the cache between runs. When added to the constructor it becomes the problem of the TestRunner which creates the TestSuiteSorter.

Thank you for your detailed comments! I will spend more time experimenting with the code and structure later today.

@@ -51,7 +51,7 @@
];

/**
* @var array
* @var array[string]int Associative array of (string => DEFECT_SORT_WEIGHT) elements
Copy link
Contributor

@keradus keradus May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array[string]int is not proper type according to phpdoc
array<string, int>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Where did you find this syntax? I saw this recommended a few times, but have found no definitive answer in the manual https://docs.phpdoc.org/guides/types.html

I found some discussions regarding proposals but most of them seem to have gone to sleep, for example: phpDocumentor/phpDocumentor#650

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

psr-5 always-proposal, sadly. but it's widely suported by IDEs and SCAs

return;
}

$this->loadFromJSON($json);
$cache = \unserialize($cacheData);
Copy link
Contributor

@keradus keradus May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please provide 2nd argument with allowed class deserialization.
now, you are exposing injection point for security hole (for PHPUnit it's not big deal, but still...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes thx :) had added that in the meantime. Since you're here, is it very picky about namespacing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self::class takes FQCN into consideration, so all good ;)

public function testCacheFilenameViaEnv()
{
$_ENV['PHPUNIT_RESULT_CACHE'] = '/some/cache';
$cache = new TestResultCache;

$this->assertEquals('/some/cache', $cache->getResultCacheFilename());
unset($_ENV['PHPUNIT_RESULT_CACHE']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if assertEquals would fail,
this line would never be executed

private $runState = [];

/**
* @var TestResultCache
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new ResultCacheListener()

boom ! this property is null, not TestResultCache ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some phpdoc ...which I then even had to sort 🤓

/**
* @var TestResultCache
*/
private $cache;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still.

new TestSuiteSorter()

and this property is null, not TestResultCache

use ?TestResultCache or null|TestResultCache

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be a few more of those, I will check and change my code style for this.

@epdenouden
Copy link
Contributor Author

@keradus Hope your offline breakfast was good :) as mentioned in the chat, here's the pull request for the defects-first project. Any comments very welcome! Small things I will take care of ASAP, larger changes to functionality for e.g. mutation testing for a next version.

@epdenouden
Copy link
Contributor Author

epdenouden commented Jun 11, 2018

Goodmorning @sebastianbergmann! The functionality and code for Breakfast are done, so let's stick a fork in it. ;-) I have been using the changes for a few days now and it looks solid.

I've checked off the list of changes you requested after I made the PR and taken care of @keradus suggestions so far.

  • the ResultCacheListener is now an extension
  • the result cache extension is added silently by using a CLI flag or configuration attribute. No more phpunit.xml changes required
  • branch can be merged and checks are green
  • test coverage has improved since the intial PR, both in functionality and %-coverage

While working on this a lot of usage scenarios have come up and I have plenty of ideas to make this more useful in the future. I have started to look at other PHP quality and testing projects such as Infection and the various scripts, listeners and printers users have created over the years.

Thanks again for all your work on PHPUnit! Hope my added functionality is useful and saves somebody somewhere a bit of time and frustration. :)

@keradus
Copy link
Contributor

keradus commented Jun 11, 2018

Sounds great ;)
If possible, please gave me some time to make a proper review @sebastianbergmann , I would like to do it this evening

@sebastianbergmann
Copy link
Owner

@epdenouden Thank you for letting me know, I hope to find the time to look at this ASAP.

In the meantime, I look forward to see @keradus' review :) Thanks, Dariusz!

/**
* @var null|TestResultCache
*/
private $cache;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is always passed via constructor now, it shall never be null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

final class ResultCacheExtension implements BeforeFirstTestHook, BeforeTestHook, AfterSuccessfulTestHook, AfterSkippedTestHook, AfterRiskyTestHook, AfterIncompleteTestHook, AfterTestErrorHook, AfterTestWarningHook, AfterTestFailureHook, AfterLastTestHook
{
/**
* @var array<string, array> array to keep track of suite/test state and timing information
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the key? it's a string, that represents.... ?
what is the value? array of.... ?

maybe some example would be nice to let us know what's inside

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By using the $time provided to the individual tests I was able to refactor the $runstate out completely. The previous ResultCacheListener also kept track of TestSuite timings, which was done using this internal run state.

$testname = $this->getTestName($test);

if (!isset($this->runState[$testname])) {
$this->runState[$testname] = [
Copy link
Contributor

@keradus keradus Jun 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo, it's a big shortcut to redirect all hooks here.
For example, it's not responsibility of executeAfterTestWarning to set start time, it shall not be in need to even check condition here.
Same for other hooks and other actions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have simplified the ResultCacheExtension quite a lot. It directly tells the cache when it has something of interest, bypassing the need for the internal state and a few hooks.

$this->stop();
}
} elseif ($t instanceof SkippedTest) {
$this->skipped[] = new TestFailure($test, $t);
$notifyMethod = 'addSkippedTest';

if ($this->stopOnSkipped) {
if ($this->stopOnSkipped || $this->stopOnDefect) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be honest not sure about this...
basically, it means that I will never use --stopOnDefect
in bigger projects, there are always some tests depending on environment, eg different version of PHP, OS, ext, ini...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for noticing! This is also a huge doubt of mine. On the one hand I want to have a shorthand for "I just want a quick edit-test-edit-test cycle right now", but it has also gotten in my way so much I had moved the "not really bad" defects down the sorter list.

For now I will remove the halting part and leave it up to the user to supply explicit --stop-on-warning, etc options

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please dont
(elaboration soon)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stopping on given type of result (~defect) shall work the same as putting them to cache to be executed first.

it's great value for me to have --stop-on-defect rather than --stop-on-* 10 times.

Only thing is to properly specify what is a defect.
For me, it all that "executed and failed" - thus all that need to manual fix from developer.
Thus, not met expectation, warning, ..., but not "skipped" or "incomplete"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this suggestion very much: the first version I made was configurable. Remember that code style fix for the defectsWeights constant? That was a leftover of the original configurable version which I thought overcomplicated at the time.

I will look for a nice way to add it back in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rereading your comment: things that need a manual fix from the developer in the test or source would be:

  • error, warning: usually PHP syntax mistakes
  • failure: assert failing
  • risky: developer forgot to add assertions to a new test

Am I reading you correctly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly.

to clarify - i'm not thinking about making it configurable again, it shall not.
defects are all that require developer fix, so exactly ones you listed 👍

@@ -1118,9 +1143,11 @@ protected function showHelp(): void
--printer <printer> TestListener implementation to use

--resolve-dependencies Resolve dependencies between tests
--defects-first-order Run previously unsuccessful tests first
--random-order Run tests in random order
--random-order-seed=<N> Use a specific random seed <N> for random order
--reverse-order Run tests last-to-first
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the outcome of setting up both, --reverse-order and --defects-first-order ? will it be respected, ignored, crashed ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like --defects-first-order overrides --random-order and --reverse-order

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually combines them. Each of the sorters has a specific preference and some are more important than others:

  1. random and reverse go first, they are considered mostly cosmetic. In theory they have no effect on the outcome.
  2. More important is sorting the defects to the front. It is an explicit request from the (human) user that indicates "I have seen what happens during the runs and I would like an explicit change to that."
  3. The dependency resolver has the last say since its job is preventing breakages. A side effect of defects first I found is getting unrelated failures as the defects were sometimes sorted the wrong 'side' of their dependants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree I need to explicitly document this behaviour and am working on a few diagrams that show the limits of the sorters when looking at how suites can be inside other suites, what dependencies really do, and so on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you need big description and diagrams, it's probably means it's overcomplicated.
maybe let us shrink it down to what would be the most value for user?

I would suggest sth like:
--order-by=default|reverse|random|defect
without possibility to mix them, like reverse + defect. I don't see much value of that combination, do you ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that would be better. My personal use case of 'randomized but with previous defects first' might be too exotic. Let's keep it simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sebastianbergmann do you have a preference. Guessing from your other comments, you prefer the simpler UX as well?

$arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null;
$arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null;
$arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false;
$arguments['cacheResult'] = $arguments['cacheResult'] ?? false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be enabled by default? this is super useful feature, why to hide it ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree! It's in my local config file. :) I'll leave that up to Sebastian if he wants it on by default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe, for that, let us enable this for now in PR, and see Sebastian's overall review ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright let's go


default:
$result['executionOrderDefects'] = TestSuiteSorter::ORDER_DEFAULT;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dummy lines doesn't look needed here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This supports using the config attribute executionOrderDefects='default' which can happen when automated pipelines do something like executionOrderDefects='$value'. I have added a test for it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the empty ("dummy") line number 960

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that was way too simple! Even your comments on whitespace scare me into making more tests haha. Removed btw

*
* <code>
* // Record running time for test
* $this->times[$testName] = 1.234;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing </code>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

/**
* Alternative path and filename for result cache file
*
* @var null|string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when could this be null, if it fallbacks to DEFAULT_RESULT_CACHE_FILENAME ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, cleaned up. In previous versions null was used to the default as a fallback in another part of the code.

$suite->run($this->result);

$this->assertEquals(BaseTestRunner::STATUS_WARNING, $this->cache->getState('Warning'));
$this->assertEquals(0, $this->cache->getTime('test_name'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertSame please. globally

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed a few, fixed!

$cache->load();

$this->assertEquals(BaseTestRunner::STATUS_UNKNOWN, $cache->getState('testOne'));
$this->assertEquals(BaseTestRunner::STATUS_SKIPPED, $cache->getState('testFive'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertSame please, globally

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx, got 'm

{
/**
* @var array<string, array> array to keep track of suite/test state and timing information
*/
private $runState = [];
Copy link
Contributor

@keradus keradus Jun 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping @epdenouden ,
dropping this property will save a lot of memory for huge testbase (like 15023 of mine), good job 👍

@@ -68,6 +68,8 @@ class Command
protected $longOptions = [
'atleast-version=' => null,
'bootstrap=' => null,
'cache-result' => null,
'cache-result-file=' => null,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we really want this to be configurable using CLI options. If caching could only be turned on through phpunit.xml then the hidden cache file could be placed next to it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If caching could only be turned on through phpunit.xml

cache-result is to enable cache via CLI

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that. What I am not sure about is the value of being able to turn on caching on the commandline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm yes. I haven't really turned it off anymore since it became stable to use. 🤔
The cache-result-file I would leave in, have found it to come in handy when scripting, like -c <config>.

@@ -505,6 +519,11 @@ protected function handleArguments(array $argv): void

break;

case '--stop-on-defect':

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand why --stop-on-defect is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly it isn't. I added it as a shorthand for a combination of --stop-on-$status flags. @keradus do you have a more compelling explanation?

Copy link
Contributor

@keradus keradus Jun 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With current definition of "defect", it's nothing more than a shortcut for putting down --stop-on-* multiple times.
It's not mandatory to have, but it's super nice shortcut for fast fail, eg for CLI.
(a lot of users runs all tests in local env, but want to fail on first issue on CI level)

So, I agree it's not needed. It's just to enhance UX.

@@ -1133,6 +1160,7 @@ protected function showHelp(): void
--include-path <path(s)> Prepend PHP's include_path with given path(s)
-d key[=value] Sets a php.ini value
--generate-configuration Generate configuration file with suggested settings
--cache-result-file==<FILE> Specify result cache path and filename

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about "Specify path to result cache file"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It contains a the full $path . $filename not just a path. The filename itself doesn't have to be .phpunit.result.cache

$arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60;
$arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10;
$arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1;
$arguments['verbose'] = $arguments['verbose'] ?? false;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this whitespace change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me neither, to be honest. I have just removed all whitespace between ] and = and had it reformatted. Looks like the assignment alignment doesn't remove excess whitespace once it has been introduced. Will commit a WS fix

@epdenouden
Copy link
Contributor Author

epdenouden commented Jun 13, 2018

@keradus @sebastianbergmann In 11360b3 I have added a suggestion for what a cleaner --order-by CLI flag could look like. Having tried it out yesterday evening while working it feels easy to use. Much better than the current clutter of --defects-first-order, --resolve-dependencies and --<sort>-order.

image

Notes:

  • I have kept the existing flags introduced in 7.2 to not break current installations
  • TextUI\Command is not easy to test without doing functional tests or reflection
  • maybe rename flag, finalize --help message

--defects-first-order Run previously unsuccessful tests first
--random-order Run tests in random order
--order-by=<order> Run tests in 'default', 'random' or 'reverse' order
--order-by=defects Run previously failed tests first
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this docs here. it's single option, it shall have single doc entry, in which we explain what is defects

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, looks like depends is missing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points, yes. Wanted to give it a quick try using it myself for a while first. If this is something that @sebastianbergmann also likes I will make it production-ready.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It barely fits on one line now, but everybody reads the manual... right? :)


private function handleOrderByOption(string $value): void
{
foreach (\explode(',', $value) as $order) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I would put as value foobar, it shall fail. now, it's ignored

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I didn't see other --options throw nice error messages yesterday. Will have a look at how to do this nicely. Silent failures are a big no-no here, I have typed defecst waaaaay too often

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a simple error message with failure exit code.

@epdenouden
Copy link
Contributor Author

@keradus The --order-by=foobar will now exit with an error message:

./phpunit --order-by=defects,foobar,depends
PHPUnit 7.3-g0a4a8c258 by Sebastian Bergmann and contributors.

unrecognized order-by option -- foobar

The help message is now:

  --order-by=<order>          Run tests in order: default|reverse|random|defects|depends

@@ -1347,6 +1346,8 @@ private function handleOrderByOption(string $value): void
$this->arguments['resolveDependencies'] = true;

break;
default:
$this->exitWithErrorMessage("unrecognized order-by option -- $order");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why double -- ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Was a leftover from where I found this in GetOpt.

phpunit.xsd Outdated
@@ -171,6 +171,12 @@
<xs:enumeration value="random"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="executionOrderDefectsType">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shall be removed now, and executionOrderType shall be extended to contain defects and depends

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. I have added a list of the common scenarios that make sense. Couldn't find a nice way to define the list in the XSD to help the tooling with validation and autocomplete.

@@ -1011,6 +1016,14 @@ public function stopOnSkipped(bool $flag): void
$this->stopOnSkipped = $flag;
}

/**
* Enables or disables the stopping for any defect: skipped, error, failure, warning, incomplete or risky.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this description is not up to date now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

'testFour' => ['state' => BaseTestRunner::STATUS_FAILURE, 'time' => 1],
'testFive' => ['state' => BaseTestRunner::STATUS_FAILURE, 'time' => 1],
],
['testFive', 'testOne', 'testTwo', 'testThree', 'testFour']],
Copy link
Contributor

@keradus keradus Jun 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👎
testFour and testFive failed, so I want to have them as soon as possible, with respect to their dependencies.

I would expect that order, instead:
['testFive', 'testThree', 'testFour', 'testOne', 'testTwo']
or
['testThree', 'testFour', 'testFive', 'testOne', 'testTwo']

as in the end, we claimed to run defects first, not last (vide testFour)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • testThree has dependencies on testOne and testTwo
  • testFour has a dependency on testThree

what happens:

  1. after sorting for defects the order is: 4 (failed), 5 (failed), 1, 2, 3
  2. running it like this would skip 4 because it needs to run 3 first
  3. same story for 3: it needs both 1 and 2
  4. the dependency resolver fixes this quietly for us: 5 can stay up front (=defects FIRST) but 4 goes behind 3 which goes behind 1,2 (=defects ASAP ;-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha, ok, your first 2 points were not part of description (while dependencies of testThree for TestFour was)

then, all good 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good but not good enough :) I will document the use of MultiDependencyTest in the dataproviders to avoid some of the confusion.

$arguments['processIsolation'] = $arguments['processIsolation'] ?? false;
$arguments['processUncoveredFilesFromWhitelist'] = $arguments['processUncoveredFilesFromWhitelist'] ?? false;
$arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? \time();
$arguments['registerMockObjectsFromTestArgumentsRecursively']= $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a space is missing before =
after solving that, alignment will be back to original one again

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried something like that in the past, let's see if your suggestion works nicer.

Copy link
Contributor

@keradus keradus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks damn good piece of work here !

@epdenouden
Copy link
Contributor Author

epdenouden commented Jun 21, 2018

@sebastianbergmann Hi Sebastian! @keradus and I think this pull request is done. For new ideas and improvements I will open new PRs in the near future. :)

Copy link
Contributor

@keradus keradus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double checked after squash, looks same as my local copy before squashing

@sebastianbergmann
Copy link
Owner

Thanks!

@epdenouden
Copy link
Contributor Author

🎁 thanks for the merge! :)

@keradus
Copy link
Contributor

keradus commented Jun 24, 2018

🎆

@epdenouden epdenouden deleted the breakfast branch July 11, 2018 14:29
aurelijusb added a commit to aurelijusb/kickstart that referenced this pull request May 14, 2019
Jis sugeneruojamas, kad būtų greitesnis testavimo-koregavimo ciklas.
Įjungtas standatiškai nuo PHPUnit 8

Dokumentacija:
 * sebastianbergmann/phpunit#3147
 * https://phpunit.de/announcements/phpunit-8.html
aurelijusbanelis pushed a commit to aurelijusb/kickstart that referenced this pull request May 16, 2019
Jis sugeneruojamas, kad būtų greitesnis testavimo-koregavimo ciklas.
Įjungtas standatiškai nuo PHPUnit 8

Dokumentacija:
 * sebastianbergmann/phpunit#3147
 * https://phpunit.de/announcements/phpunit-8.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/enhancement A new idea that should be implemented
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants