diff --git a/app/Models/Build.php b/app/Models/Build.php index 66a074fc9c..71c1bd5ddf 100644 --- a/app/Models/Build.php +++ b/app/Models/Build.php @@ -186,6 +186,16 @@ public function site(): BelongsTo } /** + * @return HasMany + */ + public function buildErrors(): HasMany + { + return $this->hasMany(BuildError::class, 'buildid'); + } + + /** + * @deprecated 02/09/2026 Use buildErrors() instead + * * @return HasMany */ public function basicAlerts(): HasMany @@ -194,6 +204,8 @@ public function basicAlerts(): HasMany } /** + * @deprecated 02/09/2026 Use buildErrors() instead + * * @return HasMany */ public function basicErrors(): HasMany @@ -203,6 +215,8 @@ public function basicErrors(): HasMany } /** + * @deprecated 02/09/2026 Use buildErrors() instead + * * @return HasMany */ public function basicWarnings(): HasMany @@ -212,6 +226,8 @@ public function basicWarnings(): HasMany } /** + * @deprecated 02/09/2026 Use buildErrors() instead + * * @return HasMany */ public function richAlerts(): HasMany diff --git a/app/Models/BuildError.php b/app/Models/BuildError.php new file mode 100644 index 0000000000..709b4b2c1c --- /dev/null +++ b/app/Models/BuildError.php @@ -0,0 +1,67 @@ + + */ +class BuildError extends Model +{ + /** @use HasFactory */ + use HasFactory; + + protected $table = 'builderrors'; + + public $timestamps = false; + + protected $fillable = [ + 'buildid', + 'workingdirectory', + 'sourcefile', + 'newstatus', + 'type', + 'stdoutput', + 'stderror', + 'exitcondition', + 'language', + 'targetname', + 'outputfile', + 'outputtype', + 'logline', + 'sourceline', + 'repeatcount', + ]; + + protected $casts = [ + 'buildid' => 'integer', + 'type' => 'integer', // TODO: Convert this to an enum + 'logline' => 'integer', + 'stdoutput' => 'string', + 'stderror' => 'string', + 'sourceline' => 'integer', + 'repeatcount' => 'integer', + 'newstatus' => 'boolean', + ]; +} diff --git a/app/cdash/tests/CMakeLists.txt b/app/cdash/tests/CMakeLists.txt index e328a27465..add40e1e2f 100644 --- a/app/cdash/tests/CMakeLists.txt +++ b/app/cdash/tests/CMakeLists.txt @@ -227,6 +227,8 @@ add_feature_test_in_transaction(/Feature/GraphQL/UpdateFileTypeTest) add_feature_test_in_transaction(/Feature/GraphQL/PinnedTestMeasurementTypeTest) +add_feature_test_in_transaction(/Feature/GraphQL/BuildErrorTypeTest) + add_feature_test_in_transaction(/Feature/RouteAccessTest) add_feature_test_in_transaction(/Feature/Monitor) diff --git a/database/factories/BuildErrorFactory.php b/database/factories/BuildErrorFactory.php new file mode 100644 index 0000000000..bb029ce27e --- /dev/null +++ b/database/factories/BuildErrorFactory.php @@ -0,0 +1,29 @@ + + */ +class BuildErrorFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'sourcefile' => Str::uuid()->toString(), + 'newstatus' => 0, + 'type' => 0, + 'stdoutput' => Str::uuid()->toString(), + 'stderror' => Str::uuid()->toString(), + ]; + } +} diff --git a/database/migrations/2026_02_08_003051_combine_build_errors_and_failures.php b/database/migrations/2026_02_08_003051_combine_build_errors_and_failures.php new file mode 100644 index 0000000000..477db3d579 --- /dev/null +++ b/database/migrations/2026_02_08_003051_combine_build_errors_and_failures.php @@ -0,0 +1,124 @@ +toString(); + $random_int = random_int(0, 100000); + + return [ + ['workingdirectory', $random_string, 'workingDirectory', $random_string], + ['workingdirectory', null, 'workingDirectory', null], + ['sourcefile', $random_string, 'sourceFile', $random_string], + ['type', 0, 'type', 'ERROR'], + ['type', 1, 'type', 'WARNING'], + ['stdoutput', $random_string, 'stdOutput', $random_string], + ['stderror', $random_string, 'stdError', $random_string], + ['exitcondition', $random_string, 'exitCondition', $random_string], + ['exitcondition', null, 'exitCondition', null], + ['language', $random_string, 'language', $random_string], + ['language', null, 'language', null], + ['targetname', $random_string, 'targetName', $random_string], + ['targetname', null, 'targetName', null], + ['outputfile', $random_string, 'outputFile', $random_string], + ['outputfile', null, 'outputFile', null], + ['outputtype', $random_string, 'outputType', $random_string], + ['outputtype', null, 'outputType', null], + ['logline', $random_int, 'logLine', $random_int], + ['logline', null, 'logLine', null], + ['sourceline', $random_int, 'sourceLine', $random_int], + ['sourceline', null, 'sourceLine', null], + ]; + } + + /** + * A basic test to ensure that each of the non-relationship fields works + */ + #[DataProvider('fieldValues')] + public function testBuildErrorFields(string $modelField, mixed $modelValue, string $graphqlField, mixed $graphqlValue): void + { + $project = $this->makePublicProject(); + + /** @var Build $build */ + $build = $project->builds()->create([ + 'name' => Str::uuid()->toString(), + 'uuid' => Str::uuid()->toString(), + ]); + + /** @var BuildError $buildError */ + $buildError = $build->buildErrors()->save(BuildError::factory()->make([ + $modelField => $modelValue, + ])); + + $this->graphQL(" + query build(\$id: ID) { + build(id: \$id) { + buildErrors { + edges { + node { + id + $graphqlField + } + } + } + } + } + ", [ + 'id' => $build->id, + ])->assertExactJson([ + 'data' => [ + 'build' => [ + 'buildErrors' => [ + 'edges' => [ + [ + 'node' => [ + 'id' => (string) $buildError->id, + $graphqlField => $graphqlValue, + ], + ], + ], + ], + ], + ], + ]); + } +}