diff --git a/CHANGELOG.md b/CHANGELOG.md index e9f89862f..a9b39b01f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ Features: - [reporting] Ability to expand table columns dynamically #928 - [reporting] Ability to group columns #928 - [reporting] Added `benchmark_compare` default report #928 -- [runner] Ability to filter by variant #938 +- [cli] Ability to filter by variant #938 +- [cli] Ability to filter reports #940 Improvements: diff --git a/lib/Benchmark/Metadata/BenchmarkMetadata.php b/lib/Benchmark/Metadata/BenchmarkMetadata.php index f54c86e93..ee97ed43b 100644 --- a/lib/Benchmark/Metadata/BenchmarkMetadata.php +++ b/lib/Benchmark/Metadata/BenchmarkMetadata.php @@ -13,6 +13,7 @@ namespace PhpBench\Benchmark\Metadata; use PhpBench\Model\Benchmark; +use PhpBench\Model\Subject; /** * Benchmark metadata class. @@ -98,20 +99,7 @@ public function getSubjects(): array public function filterSubjectNames(array $filters): void { foreach (array_keys($this->subjects) as $subjectName) { - $unset = true; - - foreach ($filters as $filter) { - if (preg_match( - sprintf('{^.*?%s.*?$}', $filter), - sprintf('%s::%s', $this->getClass(), $subjectName) - )) { - $unset = false; - - break; - } - } - - if (true === $unset) { + if (false === Subject::matchesPatterns($this->class, $subjectName, $filters)) { unset($this->subjects[$subjectName]); } } diff --git a/lib/Console/Command/Handler/RunnerHandler.php b/lib/Console/Command/Handler/RunnerHandler.php index b824c353d..d8da3fffa 100644 --- a/lib/Console/Command/Handler/RunnerHandler.php +++ b/lib/Console/Command/Handler/RunnerHandler.php @@ -82,17 +82,22 @@ public function __construct( $this->finder = $finder; } - public static function configure(Command $command): void + public static function configureFilters(Command $command): void { - $command->addArgument(self::ARG_PATH, InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Path to benchmark(s)'); $command->addOption(self::OPT_FILTER, [], InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Include benchmark subjects matching this filter. Matched against Fullly\Qualified\BenchmarkName::benchSubjectName. Can be a regex. Multiple filters combined with OR'); $command->addOption(self::OPT_VARIANT_FILTER, [], InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Include variants matching this filter. Matched against parameter set names. Can be a regex). Multiple values combined with OR'); + } + + public static function configure(Command $command): void + { + $command->addArgument(self::ARG_PATH, InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Path to benchmark(s)'); $command->addOption(self::OPT_GROUP, [], InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Group to run (can be specified multiple times)'); $command->addOption(self::OPT_PARAMETERS, null, InputOption::VALUE_REQUIRED, 'Override parameters to use in (all) benchmarks'); $command->addOption(self::OPT_ASSERT, null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Override assertions'); $command->addOption(self::OPT_FORMAT, null, InputOption::VALUE_REQUIRED, 'Set progress logger format'); $command->addOption(self::OPT_REVS, null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Override number of revs (revolutions) on (all) benchmarks'); $command->addOption(self::OPT_PROGRESS, 'l', InputOption::VALUE_REQUIRED, 'Progress logger to use'); + self::configureFilters($command); // command option is parsed before the container is compiled. $command->addOption(self::OPT_BOOTSTRAP, 'b', InputOption::VALUE_REQUIRED, 'Set or override the bootstrap file.'); diff --git a/lib/Console/Command/Handler/SuiteCollectionHandler.php b/lib/Console/Command/Handler/SuiteCollectionHandler.php index a1b755174..4b20ab5fb 100644 --- a/lib/Console/Command/Handler/SuiteCollectionHandler.php +++ b/lib/Console/Command/Handler/SuiteCollectionHandler.php @@ -60,6 +60,9 @@ public function suiteCollectionFromInput(InputInterface $input): SuiteCollection assert(is_array($files)); assert(is_array($refs)); + $subjectPatterns = $input->hasOption('filter') ? $input->getOption('filter') : []; + $variantPatterns = $input->hasOption('variant') ? $input->getOption('variant') : []; + if (!$files && !$refs) { throw new \InvalidArgumentException( 'You must specify at least one of `--file` and/or `--ref`' @@ -78,7 +81,7 @@ public function suiteCollectionFromInput(InputInterface $input): SuiteCollection foreach ($refs as $ref) { $collection->mergeCollection($this->storage->getService()->fetch( $this->refResolver->resolve($ref) - )); + )->filter($subjectPatterns, $variantPatterns)); } } diff --git a/lib/Console/Command/ReportCommand.php b/lib/Console/Command/ReportCommand.php index c4900efc1..c07c2fe15 100644 --- a/lib/Console/Command/ReportCommand.php +++ b/lib/Console/Command/ReportCommand.php @@ -14,6 +14,7 @@ use PhpBench\Console\Command\Handler\DumpHandler; use PhpBench\Console\Command\Handler\ReportHandler; +use PhpBench\Console\Command\Handler\RunnerHandler; use PhpBench\Console\Command\Handler\SuiteCollectionHandler; use PhpBench\Console\Command\Handler\TimeUnitHandler; use Symfony\Component\Console\Command\Command; @@ -87,6 +88,7 @@ public function configure(): void TimeUnitHandler::configure($this); SuiteCollectionHandler::configure($this); DumpHandler::configure($this); + RunnerHandler::configureFilters($this); } public function execute(InputInterface $input, OutputInterface $output): int diff --git a/lib/Model/Benchmark.php b/lib/Model/Benchmark.php index 8e3200839..465cab56c 100644 --- a/lib/Model/Benchmark.php +++ b/lib/Model/Benchmark.php @@ -14,6 +14,7 @@ use ArrayIterator; use PhpBench\Benchmark\Metadata\SubjectMetadata; +use RuntimeException; /** * Benchmark metadata class. @@ -74,6 +75,17 @@ public function createSubject(string $name): Subject return $subject; } + public function addSubject(Subject $subject): void + { + if ($subject->getBenchmark() !== $this) { + throw new RuntimeException( + 'Adding subject to benchmark to which it does not belong' + ); + } + + $this->subjects[$subject->getName()] = $subject; + } + /** * Get the subject metadata instances for this benchmark metadata. * @@ -120,4 +132,23 @@ public function getSubject(string $subjectName): ?Subject { return $this->subjects[$subjectName] ?? null; } + + /** + * @param string[] $subjectPatterns + * @param string[] $variantPatterns + */ + public function filter(array $subjectPatterns, array $variantPatterns): self + { + $subjects = array_filter($this->subjects, function (Subject $subject) use ($subjectPatterns) { + return Subject::matchesPatterns($this->class, $subject->getName(), $subjectPatterns); + }); + $subjects = array_map(function (Subject $subject) use ($variantPatterns) { + return $subject->filterVariants($variantPatterns); + }, $subjects); + + $new = clone $this; + $new->subjects = $subjects; + + return $new; + } } diff --git a/lib/Model/Subject.php b/lib/Model/Subject.php index 27d2522d6..ab17caad2 100644 --- a/lib/Model/Subject.php +++ b/lib/Model/Subject.php @@ -13,6 +13,7 @@ namespace PhpBench\Model; use PhpBench\Util\TimeUnit; +use RuntimeException; /** * Subject representation. @@ -118,6 +119,16 @@ public function createVariant(ParameterSet $parameterSet, int $revolutions, int return $variant; } + public function setVariant(Variant $variant): void + { + if ($variant->getSubject() !== $this) { + throw new RuntimeException( + 'Adding variant to subject to which it does not belong' + ); + } + $this->variants[$variant->getParameterSet()->getName()] = $variant; + } + /** * @return Variant[] */ @@ -235,4 +246,38 @@ public function getFormat(): ?string { return $this->format; } + + /** + * @param string[] $variantPatterns + */ + public function filterVariants(array $variantPatterns): self + { + $new = clone $this; + $new->variants = array_filter($this->variants, function (Variant $variant) use ($variantPatterns) { + return $variant->getParameterSet()->nameMatches($variantPatterns); + }); + + return $new; + } + + /** + * @param string[] $patterns + */ + public static function matchesPatterns(string $benchmark, string $subject, array $patterns): bool + { + if (empty($patterns)) { + return true; + } + + foreach ($patterns as $pattern) { + if (preg_match( + sprintf('{^.*?%s.*?$}', $pattern), + sprintf('%s::%s', $benchmark, $subject) + )) { + return true; + } + } + + return false; + } } diff --git a/lib/Model/Suite.php b/lib/Model/Suite.php index cab8e68ff..95b8f32ad 100644 --- a/lib/Model/Suite.php +++ b/lib/Model/Suite.php @@ -17,6 +17,7 @@ use IteratorAggregate; use PhpBench\Assertion\VariantAssertionResults; use PhpBench\Environment\Information; +use RuntimeException; /** * Represents a Suite. @@ -31,6 +32,10 @@ class Suite implements IteratorAggregate private $date; private $configPath; private $envInformations = []; + + /** + * @var Benchmark[] + */ private $benchmarks = []; private $uuid; @@ -70,6 +75,21 @@ public function getBenchmark(string $class): ?Benchmark return $this->benchmarks[$class] ?? null; } + /** + * @param string[] $subjectPatterns + * @param string[] $variantPatterns + */ + public function filter(array $subjectPatterns, array $variantPatterns): self + { + $new = clone $this; + $benchmarks = array_map(function (Benchmark $benchmark) use ($subjectPatterns, $variantPatterns) { + return $benchmark->filter($subjectPatterns, $variantPatterns); + }, $this->benchmarks); + $new->benchmarks = $benchmarks; + + return $new; + } + /** * Create and add a benchmark. * @@ -82,6 +102,17 @@ public function createBenchmark(string $class): Benchmark return $benchmark; } + public function addBenchmark(Benchmark $benchmark): void + { + if ($benchmark->getSuite() !== $this) { + throw new RuntimeException( + 'Adding benchmark to suite to which it does not belong' + ); + } + + $this->benchmarks[$benchmark->getClass()] = $benchmark; + } + /** * @return ArrayIterator */ diff --git a/lib/Model/SuiteCollection.php b/lib/Model/SuiteCollection.php index acaceeca5..e56f4be51 100644 --- a/lib/Model/SuiteCollection.php +++ b/lib/Model/SuiteCollection.php @@ -99,4 +99,18 @@ public function firstOnly(): self $this->suites[0] ]); } + + /** + * @param string[] $subjectPatterns + * @param string[] $variantPatterns + */ + public function filter(array $subjectPatterns, array $variantPatterns): self + { + $new = clone $this; + $new->suites = array_map(function (Suite $suite) use ($subjectPatterns, $variantPatterns) { + return $suite->filter($subjectPatterns, $variantPatterns); + }, $this->suites); + + return $new; + } } diff --git a/tests/System/ReportTest.php b/tests/System/ReportTest.php index f872d26da..bf6074c0d 100644 --- a/tests/System/ReportTest.php +++ b/tests/System/ReportTest.php @@ -43,6 +43,18 @@ public function testGenerateReportFromUuid(): void $this->assertStringContainsString('benchNothing', $output); } + public function testGenerateFilteredReport(): void + { + $document = $this->getBenchResult(null, ' --store'); + $ref = $document->evaluate('string(./suite/@uuid)'); + $process = $this->phpbench( + 'report --ref=' . $ref . ' --report=default --filter=Anything --variant=nothing' + ); + $this->assertEquals(0, $process->getExitCode()); + $output = $process->getOutput(); + $this->assertEmpty($output); + } + /** * It should allow the mode, precision and time-unit to be specified. */ diff --git a/tests/Unit/Model/SuiteTest.php b/tests/Unit/Model/SuiteTest.php index 6efc9415e..e6bf4fa70 100644 --- a/tests/Unit/Model/SuiteTest.php +++ b/tests/Unit/Model/SuiteTest.php @@ -12,37 +12,24 @@ namespace PhpBench\Tests\Unit\Model; -use PhpBench\Assertion\VariantAssertionResults; -use PhpBench\Environment\Information; use PhpBench\Model\Benchmark; +use PhpBench\Model\Error; use PhpBench\Model\ErrorStack; -use PhpBench\Model\Iteration; use PhpBench\Model\ParameterSet; -use PhpBench\Model\Subject; +use PhpBench\Model\Result\TimeResult; use PhpBench\Model\Suite; -use PhpBench\Model\Variant; use PhpBench\Tests\TestCase; +use PhpBench\Tests\Util\SuiteBuilder; class SuiteTest extends TestCase { - private $env1; - private $bench1; - - protected function setUp(): void - { - $this->env1 = $this->prophesize(Information::class); - $this->bench1 = $this->prophesize(Benchmark::class); - $this->subject1 = $this->prophesize(Subject::class); - $this->variant1 = $this->prophesize(Variant::class); - $this->iteration1 = $this->prophesize(Iteration::class); - } - /** * It should add a benchmark. */ public function testCreateBenchmark(): void { - $benchmark = $this->createSuite([])->createBenchmark('FooBench'); + $suite = SuiteBuilder::create('foo')->build(); + $benchmark = $suite->createBenchmark('FooBench'); $this->assertInstanceOf('PhpBench\Model\Benchmark', $benchmark); } @@ -53,23 +40,28 @@ public function testCreateBenchmark(): void */ public function testGetIterations(): void { - $this->bench1->getSubjects()->willReturn([$this->subject1->reveal()]); - $this->subject1->getVariants()->willReturn([$this->variant1->reveal()]); - $this->variant1->getIterator()->willReturn(new \ArrayIterator([$this->iteration1->reveal()])); - - $suite = $this->createSuite([ - $this->bench1->reveal(), - ], [ - $this->env1->reveal(), - ]); - - $this->assertSame([$this->iteration1->reveal()], $suite->getIterations()); - $this->assertSame([ - $this->variant1->reveal(), - ], $suite->getVariants()); - $this->assertSame([ - $this->subject1->reveal(), - ], $suite->getSubjects()); + $suite = SuiteBuilder::create('suite1') + ->benchmark('one') + ->subject('one') + ->variant('1') + ->iteration()->setResult(new TimeResult(1, 1))->end() + ->iteration()->setResult(new TimeResult(2, 2))->end() + ->end() + ->variant('2') + ->iteration()->setResult(new TimeResult(10, 1))->end() + ->end() + ->end() + ->end() + ->benchmark('two') + ->subject('one') + ->variant('1')->iteration()->setResult(new TimeResult(10, 1))->end()->end() + ->variant('2')->iteration()->setResult(new TimeResult(10, 1))->end()->end() + ->end() + ->end() + ->build(); + + + self::assertCount(5, $suite->getIterations()); } /** @@ -77,21 +69,21 @@ public function testGetIterations(): void */ public function testGetErrorStacks(): void { - $errorStack = $this->prophesize(ErrorStack::class); - $this->bench1->getSubjects()->willReturn([$this->subject1->reveal()]); - $this->subject1->getVariants()->willReturn([$this->variant1->reveal()]); - $this->variant1->hasErrorStack()->willReturn(true); - $this->variant1->getErrorStack()->willReturn($errorStack->reveal()); - - $suite = $this->createSuite([ - $this->bench1->reveal(), - ], [ - $this->env1->reveal(), - ]); - - $this->assertSame([ - $errorStack->reveal(), - ], $suite->getErrorStacks()); + $suite = SuiteBuilder::create('suite1') + ->benchmark('one') + ->subject('one') + ->variant('1') + ->withError(Error::fromException(new \Exception('Hello'))) + ->end() + ->end() + ->end() + ->build(); + + $stacks = $suite->getErrorStacks(); + self::assertCount(1, $stacks); + $stack = reset($stacks); + assert($stack instanceof ErrorStack); + self::assertEquals('Hello', $stack->getTop()->getMessage()); } /** @@ -99,24 +91,21 @@ public function testGetErrorStacks(): void */ public function testGetSummary(): void { - $errorStack = $this->prophesize(ErrorStack::class); - $this->bench1->getSubjects()->willReturn([$this->subject1->reveal()]); - $this->subject1->getVariants()->willReturn([$this->variant1->reveal()]); - $this->variant1->hasErrorStack()->willReturn(true); - $this->variant1->count()->willReturn(1); - $this->variant1->getSubject()->willReturn($this->subject1->reveal()); - $this->variant1->getRevolutions()->willReturn(10); - $this->variant1->getRejectCount()->willReturn(0); - $this->variant1->getRejectCount()->willReturn(0); - $this->variant1->getAssertionResults()->willReturn(new VariantAssertionResults($this->variant1->reveal(), [])); - $this->variant1->getErrorStack()->willReturn($errorStack->reveal()); - $errorStack->count()->willReturn(0); - - $suite = $this->createSuite([ - $this->bench1->reveal(), - ], [ - $this->env1->reveal(), - ]); + $suite = SuiteBuilder::create('suite1') + ->benchmark('one') + ->subject('one') + ->variant('1')->iteration()->setResult(new TimeResult(10, 1))->end()->end() + ->variant('2')->iteration()->setResult(new TimeResult(10, 1))->end()->end() + ->end() + ->end() + ->benchmark('two') + ->subject('one') + ->variant('1')->iteration()->setResult(new TimeResult(10, 1))->end()->end() + ->variant('2')->iteration()->setResult(new TimeResult(10, 1))->end()->end() + ->end() + ->end() + ->build(); + $summary = $suite->getSummary(); $this->assertInstanceOf('PhpBench\Model\Summary', $summary); @@ -138,6 +127,68 @@ public function testFindVariant(): void )); } + public function testFilterBySubjectNames(): void + { + $suite = SuiteBuilder::create('test') + ->benchmark('Foobar') + ->subject('subject_one')->end() + ->subject('subject_two')->end() + ->end() + ->build(); + + self::assertCount(2, $suite->getSubjects()); + $suite = $suite->filter(['subject_one'], []); + self::assertCount(1, $suite->getSubjects()); + } + + public function testFilterByBenchmarkNames(): void + { + $suite = SuiteBuilder::create('test') + ->benchmark('Foobar') + ->subject('subject_one')->end() + ->subject('subject_two')->end() + ->end() + ->benchmark('Bazboo') + ->subject('subject_one')->end() + ->subject('subject_two')->end() + ->end() + ->build(); + + self::assertCount(4, $suite->getSubjects(), 'Pre filter'); + $suite = $suite->filter(['Foobar'], []); + self::assertCount(2, $suite->getSubjects(), 'Post filter'); + } + + public function testFilterByVariants(): void + { + $suite = SuiteBuilder::create('test') + ->benchmark('Foobar') + ->subject('subject_one') + ->variant('variant one')->end() + ->variant('variant two')->end() + ->end() + ->subject('subject_two') + ->variant('variant one')->end() + ->variant('variant two')->end() + ->end() + ->end() + ->benchmark('Bazboo') + ->subject('subject_one') + ->variant('variant one')->end() + ->variant('variant two')->end() + ->end() + ->subject('subject_two') + ->variant('variant one')->end() + ->variant('variant two')->end() + ->end() + ->end() + ->build(); + + self::assertCount(8, $suite->getVariants(), 'Pre filter'); + $suite = $suite->filter(['Bazboo'], ['variant one']); + self::assertCount(2, $suite->getVariants(), 'Post filter'); + } + private function createSuite(array $benchmarks = [], array $informations = []): Suite { return new Suite( diff --git a/tests/Util/BenchmarkBuilder.php b/tests/Util/BenchmarkBuilder.php new file mode 100644 index 000000000..6e310144a --- /dev/null +++ b/tests/Util/BenchmarkBuilder.php @@ -0,0 +1,78 @@ +name = $name; + $this->suiteBuilder = $suiteBuilder; + } + + public static function create(string $name): self + { + return new self(null, $name); + } + + public function subject(string $name): SubjectBuilder + { + $builder = SubjectBuilder::forBenchmarkBuilder($this, $name); + $this->subjectBuilders[] = $builder; + + return $builder; + } + + public function build(?Suite $suite = null): Benchmark + { + if (null === $suite) { + $suite = new Suite( + 'testSuite', + new DateTime() + ); + } + $benchmark = new Benchmark($suite, $this->name); + + foreach ($this->subjectBuilders as $builder) { + $benchmark->addSubject($builder->build($benchmark)); + } + + return $benchmark; + } + + public function end(): SuiteBuilder + { + if (null === $this->suiteBuilder) { + throw new RuntimeException( + 'This benchmark builder was not created by a suite builder, end() cannot return anything' + ); + } + + return $this->suiteBuilder; + } + + public static function forSuiteBuilder(SuiteBuilder $builder, string $name): self + { + return new self($builder, $name); + } +} diff --git a/tests/Util/SubjectBuilder.php b/tests/Util/SubjectBuilder.php new file mode 100644 index 000000000..747a08804 --- /dev/null +++ b/tests/Util/SubjectBuilder.php @@ -0,0 +1,81 @@ +name = $name; + $this->benchmarkBuilder = $benchmarkBuilder; + } + + public static function create(string $name): self + { + return new self(null, $name); + } + + public static function forBenchmarkBuilder(BenchmarkBuilder $builder, string $name): self + { + return new self($builder, $name); + } + + public function variant(string $name): VariantBuilder + { + $builder = VariantBuilder::forSubjectBuilder($this, $name); + $this->variantBuilders[] = $builder; + + return $builder; + } + + public function build(?Benchmark $benchmark): Subject + { + if (null === $benchmark) { + $suite = new Suite( + 'testSuite', + new DateTime() + ); + $benchmark = new Benchmark($suite, 'testBenchmark'); + } + + $subject = new Subject($benchmark, $this->name); + + foreach ($this->variantBuilders as $builder) { + $subject->setVariant($builder->build($subject)); + } + + return $subject; + } + + public function end(): BenchmarkBuilder + { + if (null === $this->benchmarkBuilder) { + throw new RuntimeException( + 'This subject builder was not created by a benchmark builder, end() cannot return anything' + ); + } + + return $this->benchmarkBuilder; + } +} diff --git a/tests/Util/SuiteBuilder.php b/tests/Util/SuiteBuilder.php new file mode 100644 index 000000000..fabae8804 --- /dev/null +++ b/tests/Util/SuiteBuilder.php @@ -0,0 +1,51 @@ +name = $name; + } + + public static function create(string $name): self + { + return new self($name); + } + + public function benchmark(string $name): BenchmarkBuilder + { + $builder = BenchmarkBuilder::forSuiteBuilder($this, $name); + $this->benchmarkBuilders[] = $builder; + + return $builder; + } + + public function build(): Suite + { + $suite = new Suite( + $this->name, + new DateTime() + ); + + foreach ($this->benchmarkBuilders as $builder) { + $suite->addBenchmark($builder->build($suite)); + } + + return $suite; + } +} diff --git a/tests/Util/VariantBuilder.php b/tests/Util/VariantBuilder.php index 3d5e36c07..3f7831b1a 100644 --- a/tests/Util/VariantBuilder.php +++ b/tests/Util/VariantBuilder.php @@ -4,10 +4,12 @@ use DateTime; use PhpBench\Model\Benchmark; +use PhpBench\Model\Error; use PhpBench\Model\ParameterSet; use PhpBench\Model\Subject; use PhpBench\Model\Suite; use PhpBench\Model\Variant; +use RuntimeException; final class VariantBuilder { @@ -21,14 +23,30 @@ final class VariantBuilder */ private $revs = 1; + /** + * @var SubjectBuilder|null + */ + private $subjectBuilder; - public function __construct() + /** + * @var string + */ + private $name; + + /** + * @var Error[] + */ + private $errors; + + public function __construct(?SubjectBuilder $subjectBuilder, string $name) { + $this->subjectBuilder = $subjectBuilder; + $this->name = $name; } - public static function create(): self + public static function create(string $name = 'foo'): self { - return new self(); + return new self(null, $name); } public function setRevs(int $revs): self @@ -47,20 +65,51 @@ public function iteration(): IterationBuilder })(new IterationBuilder($this)); } - public function build(): Variant + public function build(?Subject $subject = null): Variant { - $suite = new Suite( - 'testSuite', - new DateTime() - ); - $benchmark = new Benchmark($suite, 'testBenchmark'); - $subject = new Subject($benchmark, 'foo'); - $variant = new Variant($subject, ParameterSet::fromSerializedParameters('foo', []), $this->revs, 1, []); + if (null === $subject) { + $suite = new Suite( + 'testSuite', + new DateTime() + ); + $benchmark = new Benchmark($suite, 'testBenchmark'); + $subject = new Subject($benchmark, 'foo'); + } + $variant = new Variant($subject, ParameterSet::fromSerializedParameters($this->name, []), $this->revs, 1, []); foreach ($this->iterations as $iteration) { $iteration->build($variant); } + $variant->computeStats(); + + if ($this->errors) { + $variant->createErrorStack($this->errors); + } + return $variant; } + + public function end(): SubjectBuilder + { + if (null === $this->subjectBuilder) { + throw new RuntimeException( + 'This variant builder was not created by a subject builder, end() cannot return anything' + ); + } + + return $this->subjectBuilder; + } + + public static function forSubjectBuilder(SubjectBuilder $subjectBuilder, string $name): self + { + return new self($subjectBuilder, $name); + } + + public function withError(Error $error): self + { + $this->errors[] = $error; + + return $this; + } }