Skip to content

Commit

Permalink
Merge pull request #2 from pestphp/binary-download
Browse files Browse the repository at this point in the history
Binary download
  • Loading branch information
nunomaduro authored Nov 8, 2023
2 parents dcaf933 + cb398d8 commit 1e4ed17
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 86 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/bin/summary.json
/bin/k6*
/bin/*_summary.json
/bin/*_progress.json
.idea/*
.idea/codeStyleSettings.xml
composer.lock
Expand Down
Binary file removed bin/k6-linux-amd64
Binary file not shown.
Binary file removed bin/k6-linux-arm64
Binary file not shown.
Binary file removed bin/k6-macos-amd64
Binary file not shown.
Binary file removed bin/k6-macos-arm64
Binary file not shown.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"php": "^8.2",
"pestphp/pest": "^2.24.2",
"pestphp/pest-plugin": "^2.1.1",
"ext-curl": "*"
"ext-curl": "*",
"ext-zip": "*"
},
"autoload": {
"psr-4": {
Expand Down
52 changes: 0 additions & 52 deletions src/Binary.php

This file was deleted.

21 changes: 0 additions & 21 deletions src/Contracts/Block.php

This file was deleted.

169 changes: 169 additions & 0 deletions src/K6.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
<?php

declare(strict_types=1);

namespace Pest\Stressless;

use PharData;
use RuntimeException;
use Stringable;
use ZipArchive;

final readonly class K6 implements Stringable
{
/**
* The path to the bin directory.
*/
protected const BIN_DIR = __DIR__.'/../bin/';

/**
* The version of k6 to download.
*/
public const K6_VERSION = 'v0.47.0';

/**
* The path where the k6 binary is stored relative to the root
* directory. The arguments are (version, os, arch, extension).
*/
private const K6 = 'k6-%s-%s-%s/k6%s';

/**
* The URL to the k6 binary. The arguments are (version, version, os, arch, extension).
*/
private const K6_URL = 'https://github.com/grafana/k6/releases/download/%s/k6-%s-%s-%s.%s';

/**
* Creates a new binary instance.
*/
private function __construct(private string $path)
{
//
}

/**
* Creates a new binary instance from the environment.
*/
public static function make(): self
{
$path = self::path();

if (! self::exists()) {
self::download();
}

return new self((string) realpath($path));
}

private static function path(): string
{
$os = self::os();
$arch = self::arch();

return self::BIN_DIR.sprintf(self::K6, self::K6_VERSION, $os, $arch, ($os === 'windows' ? '.exe' : ''));
}

public static function exists(): bool
{
return file_exists(self::path());
}

public static function download(): void
{
if (self::exists()) {
return;
}

$os = self::os();
$arch = self::arch();

$extension = ($os === 'linux' ? 'tar.gz' : 'zip');
$url = sprintf(self::K6_URL, self::K6_VERSION, self::K6_VERSION, $os, $arch, $extension);
$fileName = basename($url);

if (false === ($binary = file_get_contents($url))) {
throw new RuntimeException('Unable to download k6 binary.');
}

if (file_put_contents(self::BIN_DIR.$fileName, $binary) === false) {
throw new RuntimeException('Unable to save k6 binary.');
}

match ($extension) {
'tar.gz' => self::extractTarGz($fileName),
'zip' => self::extractZip($fileName)
};

if (! self::ensureExecutable(self::path())) {
throw new RuntimeException('Unable to make k6 binary executable.');
}
}

/**
* Extracts the downloaded tar.gz archive
*/
private static function extractTarGz(string $fileName): void
{
$tarGz = new PharData(self::BIN_DIR.$fileName);
$tarGz->decompress();

$tar = new PharData(self::BIN_DIR.str_replace('.gz', '', $fileName));
$tar->extractTo(self::BIN_DIR);

unlink(self::BIN_DIR.str_replace('.gz', '', $fileName));
unlink(self::BIN_DIR.$fileName);
}

/**
* Extracts the downloaded zip archive
*/
private static function extractZip(string $fileName): void
{
$zip = new ZipArchive();

if ($zip->open(self::BIN_DIR.$fileName) !== true) {
throw new RuntimeException('Unable to open k6 zip archive.');
}

$zip->extractTo(self::BIN_DIR);
$zip->close();

unlink(self::BIN_DIR.$fileName);
}

/**
* Returns the computer's architecture.
*/
private static function arch(): string
{
return str_contains(php_uname('m'), 'arm') ? 'arm64' : 'amd64';
}

/**
* Returns the operating system.
*/
private static function os(): string
{
return match (PHP_OS_FAMILY) {
'Darwin' => 'macos',
'Linux' => 'linux',
'Windows' => 'windows',
default => throw new RuntimeException('Unsupported OS.'),
};
}

/**
* Make the binary executable.
*/
private static function ensureExecutable(string $binary): bool
{
return chmod($binary, 0755);
}

/**
* The string representation of the binary.
*/
public function __toString(): string
{
return $this->path;
}
}
11 changes: 6 additions & 5 deletions src/Printers/Detail.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Pest\Stressless\Result;

use function Termwind\render;
use function Termwind\terminal;

/**
* @internal
Expand All @@ -31,18 +30,20 @@ public function print(Result $result): void
$color = $this->color($result->requests->dnsLookup->duration->avg, 20.0, 50.0, 100.0);
$value = $this->ms($result->requests->dnsLookup->duration->avg);

// map all IPv4 and IPv6 addresses of the given domain
$domain = $result->url();
$domain = (string) parse_url($domain, PHP_URL_HOST);
$dnsRecords = dns_get_record($domain, DNS_AAAA + DNS_A);
$dnsRecords = array_map(fn (array $record): string => $record['ipv6'] ?? $record['ip'], $dnsRecords ?: []);
$dnsRecords = array_unique($dnsRecords);
$dnsRecords = implode(', ', $dnsRecords);

if (strlen($dnsRecords) > 0 && strlen($dnsRecords) > ($size = terminal()->width() - 30)) {
$dnsRecords = substr($dnsRecords, 0, $size).'(…)';
if (count($dnsRecords) > 2) {
$lastDnsRecord = '(+ '.(count($dnsRecords) - 2).' more)';
$dnsRecords = array_slice($dnsRecords, 0, 2);
$dnsRecords[] = $lastDnsRecord;
}

$dnsRecords = implode(', ', $dnsRecords);

$this->twoColumnDetail('DNS Lookup Duration', <<<HTML
<span class="text-gray mr-1">$dnsRecords</span>
<span class="$color">$value</span>
Expand Down
15 changes: 14 additions & 1 deletion src/Run.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use RuntimeException;
use Symfony\Component\Process\Process;

use function Termwind\render;

/**
* @internal
*/
Expand Down Expand Up @@ -48,8 +50,19 @@ public function start(): Result
$duration,
);

if (! K6::exists()) {
render(<<<'HTML'
<div class="mx-2 mt-1">
<span class="bg-cyan font-bold px-1 mr-1">INFO</span>
<span class="font-bold">Hang tight — we're setting things up for your first stress test!</span>
</div>
HTML);

K6::download();
}

$process = new Process([
Binary::k6(), 'run', 'run.js', '--out', "json={$this->session->progressPath()}",
K6::make(), 'run', 'run.js', '--out', "json={$this->session->progressPath()}",
], $basePath.'/bin', [
'PEST_STRESS_TEST_OPTIONS' => json_encode($this->options, JSON_THROW_ON_ERROR),
'PEST_STRESS_TEST_URL' => $this->url,
Expand Down
10 changes: 5 additions & 5 deletions tests/Unit/Binary.php → tests/Unit/K6.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

declare(strict_types=1);

use Pest\Stressless\Binary;
use Pest\Stressless\K6;

it('infers the path from the environment on mac OS', function (): void {
$binary = Binary::k6();
$binary = K6::make();

$arch = str_contains(php_uname('m'), 'arm') ? 'arm64' : 'amd64';

expect((string) $binary)->toBe(realpath(__DIR__.'/../../bin/k6-macos-'.$arch));
expect((string) $binary)->toBe(realpath(__DIR__.'/../../bin/k6-'.k6::K6_VERSION.'-macos-'.$arch.'/k6'));
})->skipOnLinux()->skipOnWindows();

it('infers the path from the environment on Linux', function (): void {
$binary = Binary::k6();
$binary = K6::make();

$arch = str_contains(php_uname('m'), 'arm') ? 'arm64' : 'amd64';

expect((string) $binary)->toBe(realpath(__DIR__.'/../../bin/k6-linux-'.$arch));
expect((string) $binary)->toBe(realpath(__DIR__.'/../../bin/k6-'.k6::K6_VERSION.'-linux-'.$arch.'/k6'));
})->skipOnMac()->skipOnWindows();

0 comments on commit 1e4ed17

Please sign in to comment.