create(['name' => 'peterZ']); // All three runs share real configuration identity. Labels are user // notes, not grouping identity, so the different session B label must // not fork a second bubble. $a = BenchmarkRun::factory()->create([ 'suite' => 'softbench', 'label' => 'session A', 'model_id' => 'm', 'model_quant' => 'Q5_K_M', 'gpu' => 'AMD R9700', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public', 'ran_at' => '2026-09-03 12:00:00', ]); SoftbenchResult::factory()->create(['benchmark_run_id' => $a->id, 'ok' => true, 'user_tps' => 60.0, 'total' => 100, 'level' => 'off']); SoftbenchResult::factory()->create(['benchmark_run_id' => $a->id, 'ok' => false, 'user_tps' => 20.0, 'total' => 300, 'level' => 'off']); $b = BenchmarkRun::factory()->create([ 'suite' => 'infrabench', 'label' => 'session A', 'model_id' => 'm', 'model_quant' => 'Q5_K_M', 'gpu' => 'AMD R9700', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public', 'ran_at' => '2026-09-03 12:05:00', ]); InfrabenchResult::factory()->create(['benchmark_run_id' => $b->id, 'ok' => true, 'user_tps' => 40.0, 'total' => 200, 'level' => 'low']); $c = BenchmarkRun::factory()->create([ 'suite' => 'softbench', 'label' => 'session B', 'model_id' => 'm', 'model_quant' => 'Q5_K_M', 'gpu' => 'AMD R9700', 'gpu_count' => 1, 'engine' => 'llama.cpp', 'user_id' => $me->id, 'visibility' => 'public', 'ran_at' => '2026-09-04 12:00:00', ]); SoftbenchResult::factory()->create(['benchmark_run_id' => $c->id, 'ok' => true, 'user_tps' => 50.0, 'total' => 50, 'level' => 'medium']); Artisan::call('festbench:summary'); $bands = DB::table('session_summaries')->orderBy('sequence')->get(); expect($bands)->toHaveCount(1); $bandA = $bands->first(); expect($bandA->runner_name)->toBe('peterZ') ->and($bandA->gpu)->toBe('AMD R9700') ->and($bandA->gpu_count)->toBe(1) // Mixed labels intentionally show no single label on the band. ->and($bandA->label)->toBeNull() ->and($bandA->total_passed)->toBe(3) ->and($bandA->total_failed)->toBe(1) ->and($bandA->total_tokens)->toBe(650); $rowsA = DB::table('session_suite_rows')->where('session_summary_id', $bandA->id)->orderBy('sequence')->get(); expect($rowsA)->toHaveCount(3); $soft = $rowsA->where('suite', 'softbench')->firstWhere('level', 'off'); // simple avg of (60, 20) = 40; weighted = (60*100 + 20*300)/400 = 30 expect($soft->passed)->toBe(1) ->and($soft->failed)->toBe(1) ->and($soft->avg_user_tps)->toBe(40.0) ->and($soft->weighted_user_tps)->toBe(30.0) ->and($soft->total_tokens)->toBe(400) ->and($soft->level)->toBe('off'); $infra = $rowsA->firstWhere('suite', 'infrabench'); expect($infra->passed)->toBe(1)->and($infra->level)->toBe('low'); $secondSoft = $rowsA->where('suite', 'softbench')->firstWhere('level', 'medium'); expect($secondSoft->passed)->toBe(1)->and($secondSoft->total_tokens)->toBe(50); }); test('private runs form their own band and are hidden from guests', function () { $me = User::factory()->create(); BenchmarkRun::factory()->create([ 'suite' => 'softbench', 'label' => 'pub', 'model_id' => 'm', 'visibility' => 'public', 'gpu' => 'g', 'gpu_count' => 1, 'user_id' => $me->id, ]); SoftbenchResult::factory()->create(['benchmark_run_id' => BenchmarkRun::latest('id')->first()->id]); BenchmarkRun::factory()->create([ 'suite' => 'softbench', 'label' => 'pub', 'model_id' => 'm', 'visibility' => 'private', 'gpu' => 'g', 'gpu_count' => 1, 'user_id' => $me->id, ]); Artisan::call('festbench:summary'); expect(DB::table('session_summaries')->count())->toBe(2) ->and(DB::table('session_summaries')->where('visibility', 'private')->count())->toBe(1); // Guest home shows the public band only; owner sees both $this->get(route('home'))->assertOk()->assertSee('pub'); $bands = DB::table('session_summaries')->pluck('visibility'); expect($bands)->toContain('public')->toContain('private'); }); test('home renders the session summary and links into per-task detail', function () { $run = BenchmarkRun::factory()->create([ 'suite' => 'softbench', 'label' => 'rendered session', 'visibility' => 'public', 'model_id' => 'render/model', 'gpu' => 'AMD R9700', 'gpu_count' => 2, ]); SoftbenchResult::factory()->create(['benchmark_run_id' => $run->id, 'ok' => true, 'level' => 'off']); Artisan::call('festbench:summary'); $this->get(route('home'))->assertOk() ->assertSee('rendered session') ->assertSee('render/model') ->assertSee('AMD R9700') ->assertSee(route('runs.show', $run->run_id)) ->assertSee('All task rows'); }); test('results page still lists flat task rows', function () { $run = BenchmarkRun::factory()->create(['suite' => 'softbench', 'visibility' => 'public']); SoftbenchResult::factory()->create(['benchmark_run_id' => $run->id, 'task' => 'flat-task']); $this->get(route('results'))->assertOk() ->assertSee('All Task Rows') ->assertSee('flat-task'); });