Ctrl K
ComunidadGuías…

tests/Feature/BuildServerConcurrentBuildsTest.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php

use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

beforeEach(function () {
    Server::flushIdentityMap();
    Queue::fake();
    Notification::fake();
    Storage::fake('ssh-keys');
    Storage::fake('ssh-mux');
    Process::fake(['*' => Process::result(output: '')]);
    config([
        'constants.ssh.mux_enabled' => false,
        'cache.default' => 'array',
        'app.maintenance.store' => 'array',
    ]);

    $settings = new InstanceSettings;
    $settings->id = 0;
    $settings->save();

    $this->team = Team::factory()->create();
    $privateKey = PrivateKey::factory()->create(['team_id' => $this->team->id]);
    $environment = Environment::factory()->create([
        'project_id' => Project::factory()->create(['team_id' => $this->team->id])->id,
    ]);
    $ip = 40;

    $this->makeServer = function (int $concurrentBuilds, string $role = 'both') use ($privateKey, &$ip): Server {
        $server = Server::factory()->create(['team_id' => $this->team->id, 'private_key_id' => $privateKey->id, 'ip' => '203.0.113.'.$ip++]);
        $server->settings->update([
            'concurrent_builds' => $concurrentBuilds,
            'server_role' => $role,
            'is_build_server' => $role === 'build',
            'is_reachable' => true,
            'is_usable' => true,
        ]);

        return $server->fresh();
    };

    $this->makeApplication = function (Server $server, bool $usesBuildServer = true) use ($environment): Application {
        $destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail();
        $application = Application::factory()->create([
            'environment_id' => $environment->id,
            'destination_id' => $destination->id,
            'destination_type' => $destination->getMorphClass(),
        ]);
        $application->settings->update(['is_build_server_enabled' => $usesBuildServer]);

        return $application->fresh();
    };

    $this->deploy = function (Application $application): ApplicationDeploymentQueue {
        $uuid = new_public_id();
        queue_application_deployment($application, $uuid);

        return ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->firstOrFail();
    };
});

afterEach(function () {
    Server::flushIdentityMap();
});

test('the deployment server limit does not cap builds that run on a build server', function () {
    $deploymentServer = ($this->makeServer)(1);
    $buildServer = ($this->makeServer)(2, 'build');

    $first = ($this->deploy)(($this->makeApplication)($deploymentServer));
    $second = ($this->deploy)(($this->makeApplication)($deploymentServer));
    $third = ($this->deploy)(($this->makeApplication)($deploymentServer));

    expect($first->status)->toBe(ApplicationDeploymentStatus::IN_PROGRESS->value)
        ->and($first->build_server_id)->toBe($buildServer->id)
        ->and($second->status)->toBe(ApplicationDeploymentStatus::IN_PROGRESS->value)
        ->and($second->build_server_id)->toBe($buildServer->id)
        ->and($third->status)->toBe(ApplicationDeploymentStatus::QUEUED->value)
        ->and($third->build_server_id)->toBeNull();
});

test('the build server limit holds for deployments of different deployment servers', function () {
    $buildServer = ($this->makeServer)(1, 'build');

    $first = ($this->deploy)(($this->makeApplication)(($this->makeServer)(5)));
    $second = ($this->deploy)(($this->makeApplication)(($this->makeServer)(5)));

    expect($first->status)->toBe(ApplicationDeploymentStatus::IN_PROGRESS->value)
        ->and($first->build_server_id)->toBe($buildServer->id)
        ->and($second->status)->toBe(ApplicationDeploymentStatus::QUEUED->value);
    Queue::assertPushed(ApplicationDeploymentJob::class, 1);
});

test('a deployment reserves the build server that has a free build slot', function () {
    $deploymentServer = ($this->makeServer)(5);
    $buildServers = collect([($this->makeServer)(1, 'build'), ($this->makeServer)(1, 'build')]);

    $first = ($this->deploy)(($this->makeApplication)($deploymentServer));
    $second = ($this->deploy)(($this->makeApplication)($deploymentServer));

    expect([$first->build_server_id, $second->build_server_id])
        ->toEqualCanonicalizing($buildServers->pluck('id')->all())
        ->and($second->status)->toBe(ApplicationDeploymentStatus::IN_PROGRESS->value);
});

test('a finished build starts the deployment of another deployment server that waits for the build server', function () {
    $buildServer = ($this->makeServer)(1, 'build');
    $running = ($this->deploy)(($this->makeApplication)(($this->makeServer)(5)));
    $waiting = ($this->deploy)(($this->makeApplication)(($this->makeServer)(5)));
    expect($waiting->status)->toBe(ApplicationDeploymentStatus::QUEUED->value);

    (new ApplicationDeploymentJob($running->id))->failed(new RuntimeException('build failed'));

    $waiting->refresh();
    expect($waiting->status)->toBe(ApplicationDeploymentStatus::IN_PROGRESS->value)
        ->and($waiting->build_server_id)->toBe($buildServer->id);
    Queue::assertPushed(ApplicationDeploymentJob::class, fn (ApplicationDeploymentJob $job) => $job->application_deployment_queue_id === $waiting->id);
});

test('builds on a build server do not take the build slots of the deployment server', function () {
    $deploymentServer = ($this->makeServer)(1);
    ($this->makeServer)(1, 'build');

    $onBuildServer = ($this->deploy)(($this->makeApplication)($deploymentServer));
    $onDeploymentServer = ($this->deploy)(($this->makeApplication)($deploymentServer, usesBuildServer: false));
    $waiting = ($this->deploy)(($this->makeApplication)($deploymentServer, usesBuildServer: false));

    expect($onBuildServer->status)->toBe(ApplicationDeploymentStatus::IN_PROGRESS->value)
        ->and($onDeploymentServer->status)->toBe(ApplicationDeploymentStatus::IN_PROGRESS->value)
        ->and($onDeploymentServer->build_server_id)->toBeNull()
        ->and($waiting->status)->toBe(ApplicationDeploymentStatus::QUEUED->value);
});