Skip to content

Commit

Permalink
Add build measurement backend and API (#2460)
Browse files Browse the repository at this point in the history
In #2395, a user requested for
the instrumentation data collected by CMake and CTest in [CMake issue
26099](https://gitlab.kitware.com/cmake/cmake/-/issues/26099)to be
stored in CDash. This PR sets up the infrastructure needed to store and
retrieve "build measurements" in CDash. Our existing "test measurement"
functionality will be used to store the test instrumentation data. A
future PR will introduce logic to populate the new build measurements
table during the submission parsing process.
  • Loading branch information
williamjallen authored Sep 30, 2024
1 parent b4406fb commit c77bd6e
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 2 deletions.
8 changes: 8 additions & 0 deletions app/Enums/BuildMeasurementType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace App\Enums;

enum BuildMeasurementType: int
{
case TARGET = 0;
}
8 changes: 8 additions & 0 deletions app/Models/Build.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,12 @@ public function buildGroups(): BelongsToMany
{
return $this->belongsToMany(BuildGroup::class, 'build2group', 'groupid', 'buildid');
}

/**
* @return HasMany<BuildMeasurement>
*/
public function measurements(): HasMany
{
return $this->hasMany(BuildMeasurement::class, 'buildid');
}
}
46 changes: 46 additions & 0 deletions app/Models/BuildMeasurement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Models;

use App\Enums\BuildMeasurementType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;

/**
* @property int $id
* @property int $buildid
* @property string $name
* @property string $source
* @property BuildMeasurementType $type
* @property string $value
*
* @mixin Builder<BuildMeasurement>
*/
class BuildMeasurement extends Model
{
protected $table = 'buildmeasurements';

public $timestamps = false;

protected $fillable = [
'name',
'source',
'type',
'value',
];

protected $casts = [
'id' => 'integer',
'buildid' => 'integer',
'type' => BuildMeasurementType::class,
];

/**
* @return HasOne<Build>
*/
public function build(): HasOne
{
return $this->hasOne(Build::class, 'id', 'buildid');
}
}
19 changes: 19 additions & 0 deletions app/Providers/GraphQLServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Providers;

use App\Enums\BuildMeasurementType;
use Illuminate\Support\ServiceProvider;
use Nuwave\Lighthouse\Schema\TypeRegistry;
use GraphQL\Type\Definition\PhpEnumType;

final class GraphQLServiceProvider extends ServiceProvider
{
/**
* @throws \GraphQL\Error\InvariantViolation
*/
public function boot(TypeRegistry $typeRegistry): void
{
$typeRegistry->register(new PhpEnumType(BuildMeasurementType::class));
}
}
5 changes: 4 additions & 1 deletion app/cdash/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,11 @@ set_tests_properties(/Feature/GraphQL/TestMeasurementTypeTest PROPERTIES DEPENDS
add_laravel_test(/Feature/GraphQL/NoteTypeTest)
set_tests_properties(/Feature/GraphQL/NoteTypeTest PROPERTIES DEPENDS /Feature/GraphQL/BuildTypeTest)

add_laravel_test(/Feature/GraphQL/BuildMeasurementTypeTest)
set_tests_properties(/Feature/GraphQL/BuildMeasurementTypeTest PROPERTIES DEPENDS /Feature/GraphQL/BuildTypeTest)

add_laravel_test(/Feature/PurgeUnusedProjectsCommand)
set_tests_properties(/Feature/PurgeUnusedProjectsCommand PROPERTIES DEPENDS "/Feature/GraphQL/TestTypeTest;/Feature/GraphQL/TestMeasurementTypeTest;/Feature/GraphQL/NoteTypeTest")
set_tests_properties(/Feature/PurgeUnusedProjectsCommand PROPERTIES DEPENDS "/Feature/GraphQL/TestTypeTest;/Feature/GraphQL/TestMeasurementTypeTest;/Feature/GraphQL/NoteTypeTest;/Feature/GraphQL/BuildMeasurementTypeTest")

add_laravel_test(/Feature/TestSchemaMigration)
set_tests_properties(/Feature/TestSchemaMigration PROPERTIES DEPENDS /Feature/PurgeUnusedProjectsCommand)
Expand Down
2 changes: 1 addition & 1 deletion config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
/**
* GraphQL Service Providers
*/
\Nuwave\Lighthouse\LighthouseServiceProvider::class,
App\Providers\GraphQLServiceProvider::class,
],

/*
Expand Down
36 changes: 36 additions & 0 deletions database/migrations/2024_09_24_184156_build_measurements_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('buildmeasurements', function (Blueprint $table) {
$table->id();
$table->integer('buildid')->nullable(false);
$table->smallInteger('type')->nullable(false);
$table->string('name', 511)->nullable(false);
$table->string('source', 511)->nullable(false);
$table->string('value', 255)->nullable(false);

$table->foreign('buildid')->references('id')->on('build')->cascadeOnDelete();
$table->index(['buildid', 'name']);
$table->index(['buildid', 'source']);
$table->index(['buildid', 'type']);
$table->index(['buildid', 'value']);
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('buildmeasurements');
}
};
34 changes: 34 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ type Build {
project: Project! @belongsTo

notes: [Note!]! @belongsToMany(type: CONNECTION) @orderBy(column: "id", direction: DESC)

measurements(
filters: _ @filter(inputType: "BuildMeasurementFilterInput")
): [BuildMeasurement!]! @belongsToMany(type: CONNECTION) @orderBy(column: "id", direction: DESC)
}

input BuildFilterInput {
Expand Down Expand Up @@ -305,6 +309,36 @@ type TestMeasurement {
}


"Build Measurement."
type BuildMeasurement {
"Unique primary key."
id: ID!

name: String!

source: String!

type: BuildMeasurementType!

"""
The value for this measurement. Even though some values may be numeric, all
values are returned as strings.
Example: "5"
"""
value: String!
}


input BuildMeasurementFilterInput {
id: ID
name: String
source: String
type: BuildMeasurementType
value: String
}


"""
"Basic" alerts are warnings or errors scraped from the build log, as opposed to "rich" alerts which
come from CTest launchers. A given build should either have all "basic" alerts or all "rich" alerts.
Expand Down
165 changes: 165 additions & 0 deletions tests/Feature/GraphQL/BuildMeasurementTypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

namespace Tests\Feature\GraphQL;

use App\Enums\BuildMeasurementType;
use App\Models\Build;
use App\Models\BuildMeasurement;
use App\Models\Project;
use Illuminate\Support\Str;
use Tests\TestCase;
use Tests\Traits\CreatesProjects;
use Tests\Traits\CreatesUsers;

class BuildMeasurementTypeTest extends TestCase
{
use CreatesUsers;
use CreatesProjects;

private Project $project;
private Project $project2;

protected function setUp(): void
{
parent::setUp();

$this->project = $this->makePublicProject();
$this->project2 = $this->makePrivateProject();
}

protected function tearDown(): void
{
// Deleting the project will delete all corresponding builds and build measurements
$this->project->delete();
$this->project2->delete();

parent::tearDown();
}

/**
* A basic test to ensure that each of the fields works
*/
public function testBasicFieldAccess(): void
{
/** @var Build $build */
$build = $this->project->builds()->create([
'name' => Str::uuid()->toString(),
'uuid' => Str::uuid()->toString(),
]);

/** @var BuildMeasurement $measurement */
$measurement = $build->measurements()->create([
'name' => Str::uuid()->toString(),
'source' => Str::uuid()->toString(),
'type' => BuildMeasurementType::TARGET,
'value' => 5,
]);

$this->graphQL('
query($id: ID) {
build(id: $id) {
measurements {
edges {
node {
id
name
source
type
value
}
}
}
}
}
', [
'id' => $build->id,
])->assertJson([
'data' => [
'build' => [
'measurements' => [
'edges' => [
[
'node' => [
'id' => (string) $measurement->id,
'name' => $measurement->name,
'source' => $measurement->source,
'type' => 'TARGET',
'value' => '5',
],
],
],
],
],
],
], true);
}

public function testMeasurementFiltering(): void
{
/** @var Build $build */
$build = $this->project->builds()->create([
'name' => Str::uuid()->toString(),
'uuid' => Str::uuid()->toString(),
]);

$build->measurements()->create([
'name' => Str::uuid()->toString(),
'source' => Str::uuid()->toString(),
'type' => BuildMeasurementType::TARGET,
'value' => 4,
]);

$build->measurements()->create([
'name' => Str::uuid()->toString(),
'source' => Str::uuid()->toString(),
'type' => BuildMeasurementType::TARGET,
'value' => 5,
]);

$build->measurements()->create([
'name' => Str::uuid()->toString(),
'source' => Str::uuid()->toString(),
'type' => BuildMeasurementType::TARGET,
'value' => 6,
]);

$this->graphQL('
query($id: ID) {
build(id: $id) {
measurements(filters: {
gt: {
value: "4"
}
}) {
edges {
node {
value
}
}
}
}
}
', [
'id' => $build->id,
])->assertJson([
'data' => [
'build' => [
'measurements' => [
'edges' => [
[
'node' => [
'value' => '6',
],
],
[
'node' => [
'value' => '5',
],
],
],
],
],
],
], true);
}
}

0 comments on commit c77bd6e

Please sign in to comment.