diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 7ad8a15..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,44 +0,0 @@ -version: 2.1 - -workflows: - build: - jobs: - - build: - name: build_lowest - composer_args: --prefer-lowest - - build: - name: build_highest - -jobs: - build: - parameters: - composer_args: - type: string - default: "" - docker: - - image: skpr/php-circleci:8.1-v2-latest - working_directory: /data - steps: - - checkout - - restore_cache: - keys: - - deps-{{ arch }} - - run: - name: "Install Dependencies" - command: composer update --prefer-dist --no-progress --no-interaction <> - - save_cache: - key: deps-{{ arch }} - paths: - - vendor - - run: - name: "Lint" - command: ./bin/phpcs - - run: - name: "Test" - command: | - mkdir -p ~/phpunit - ./bin/phpunit --testsuite unit --log-junit ~/phpunit/phpunit.xml - - store_test_results: - path: ~/phpunit - - store_artifacts: - path: ~/phpunit diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a8a48ff --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,44 @@ +name: 🏗 Build + +on: + pull_request: + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + prefer_lowest: ["", "--prefer-lowest"] + php: ["8.2", "8.5"] + container: + image: skpr/php-cli:${{ matrix.php }}-dev-v2-stable + options: + --pull always + --user 1001:1001 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + show-progress: false + - name: Composer Update + run: composer update --with-all-dependencies --prefer-dist --no-progress --no-interaction ${{ matrix.prefer_lowest }} + - name: PHPCS + # Convert emacs format (file:line:col: severity - message) to GitHub annotations (::severity file=,line=,col=::message) + run: | + ./bin/phpcs --report=emacs -q | sed -E 's/^([^:]+):([0-9]+):([0-9]+): (error|warning) - (.*)$/::\4 file=\1,line=\2,col=\3::\5/' || true + ./bin/phpcs --report=full + - name: PHPStan + run: ./bin/phpstan --error-format=github analyse + - name: Add PHPUnit matcher + run: | + cat << 'EOF' > .github/phpunit-failure.json + {"problemMatcher":[{"owner":"phpunit-failure","severity":"error","pattern":[{"regexp":"##teamcity\\[testFailed[^\\]]*message='([^']*)'[^\\]]*details='/data/([^:]+):(\\d+)","message":1,"file":2,"line":3}]}]} + EOF + echo "::add-matcher::.github/phpunit-failure.json" + - name: Run Tests + run: ./bin/phpunit --teamcity + - name: Remove PHPUnit matcher + if: always() + run: echo "::remove-matcher owner=phpunit-failure::" diff --git a/.gitignore b/.gitignore index 0d12a30..ee8bcb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /bin /vendor /composer.lock +.phpunit.cache .phpunit.result.cache diff --git a/composer.json b/composer.json index f74711c..153a2ea 100644 --- a/composer.json +++ b/composer.json @@ -9,16 +9,16 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", - "symfony/property-access": "^5.4|^6.2|^7.1", - "symfony/serializer": "^5.4|^6.2|^7.1" + "symfony/property-access": "^6.4.32|^7.4.8|^8.0.8", + "symfony/serializer": "^6.4.32|^7.4.8|^8.0.10" }, "require-dev": { - "drupal/coder": "^8.3.16", - "phpunit/phpunit": "^9.5.28", - "phpspec/prophecy": "^1.16", - "symfony/phpunit-bridge": "^5.4|^6.2|^7.1" + "drupal/coder": "^9.0.0", + "phpspec/prophecy": "^1.26.1", + "phpstan/phpstan": "^2.1.55", + "phpunit/phpunit": "^11.5.55" }, "autoload": { "psr-4": {"PNX\\Prometheus\\": "src/"} diff --git a/phpcs.xml b/phpcs.xml index cb9efcd..c0657de 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,26 +1,8 @@ PHP CodeSniffer configuration for PHP Prometheus - ./src ./tests - - - - - - - - - + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..0ce79d5 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 6 + paths: + - src + - tests diff --git a/phpunit.xml b/phpunit.xml index e3c3655..0b802c0 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,34 +1,24 @@ - - - - - - ./src - - - ./ - ./ - - + - - - ./tests/Unit - - - - - + + + ./src + + diff --git a/src/Counter.php b/src/Counter.php index 62829ee..9c59841 100644 --- a/src/Counter.php +++ b/src/Counter.php @@ -1,5 +1,7 @@ $labels * The list of key value label pairs. + * + * @throws \InvalidArgumentException + * If the value is not a non-negative integer. */ - public function set($value, array $labels = []) { + public function set(mixed $value, array $labels = []): void { if (!$this->isValidValue($value)) { throw new \InvalidArgumentException("A count value must be a positive integer."); } @@ -44,7 +49,7 @@ public function set($value, array $labels = []) { * @return bool * TRUE if the value is valid. FALSE otherwise. */ - protected function isValidValue($value) { + protected function isValidValue(mixed $value): bool { return is_int($value) && $value >= 0; } diff --git a/src/Gauge.php b/src/Gauge.php index ab26ad9..fc688e6 100644 --- a/src/Gauge.php +++ b/src/Gauge.php @@ -1,5 +1,7 @@ $labels * The list of key value label pairs. */ - public function set($value, array $labels = []) { + public function set(mixed $value, array $labels = []): void { $key = $this->getKey($labels); $this->labelledValues[$key] = new LabelledValue($this->getName(), $value, $labels); } diff --git a/src/LabelledValue.php b/src/LabelledValue.php index c625b44..5b2de7a 100644 --- a/src/LabelledValue.php +++ b/src/LabelledValue.php @@ -1,5 +1,7 @@ */ - protected $labels; + protected array $labels; /** * The name override for this labelled value. - * - * @var string */ - protected $name; + protected string $name; /** * Value constructor. @@ -40,10 +38,13 @@ class LabelledValue { * The name override for this label. * @param mixed $value * The metric value. - * @param array[] $labels + * @param array $labels * The key value pairs of labels. + * + * @throws \InvalidArgumentException + * If the name or label names are invalid. */ - public function __construct($name, $value, array $labels = []) { + public function __construct(string $name, mixed $value, array $labels = []) { $this->value = $value; foreach ($labels as $labelKey => $labelValue) { if (!preg_match(self::LABEL_NAME_REGEX, $labelKey)) { @@ -63,14 +64,14 @@ public function __construct($name, $value, array $labels = []) { * @return mixed * The Value. */ - public function getValue() { + public function getValue(): mixed { return $this->value; } /** * Gets the Labels. * - * @return array[] + * @return array * The Labels. */ public function getLabels(): array { @@ -80,10 +81,10 @@ public function getLabels(): array { /** * Gets the Name override. * - * @return null|string + * @return string * The Name. */ - public function getName() { + public function getName(): string { return $this->name; } diff --git a/src/Metric.php b/src/Metric.php index 2468948..c5a00cb 100644 --- a/src/Metric.php +++ b/src/Metric.php @@ -1,5 +1,7 @@ validateName($fullName); $this->name = $fullName; @@ -75,7 +73,7 @@ public function getName(): string { * The Help. */ public function getHelp(): string { - return $this->help; + return (string) $this->help; } /** @@ -84,7 +82,7 @@ public function getHelp(): string { * @return \PNX\Prometheus\LabelledValue[] * The array of values. */ - public function getLabelledValues() { + public function getLabelledValues(): array { return array_values($this->labelledValues); } @@ -93,8 +91,11 @@ public function getLabelledValues() { * * @param string $name * The metric or label name. + * + * @throws \InvalidArgumentException + * If the name is invalid. */ - protected function validateName($name): void { + protected function validateName(string $name): void { if (!preg_match(self::METRIC_NAME_REGEX, $name)) { throw new \InvalidArgumentException("Invalid name: '" . $name . "'"); } @@ -103,13 +104,13 @@ protected function validateName($name): void { /** * Generates a unique key for the specified labels. * - * @param array $labels + * @param array $labels * The labels. * * @return string * A unique key for the labels. */ - protected function getKey(array $labels) { + protected function getKey(array $labels): string { return md5(json_encode($labels, JSON_FORCE_OBJECT)); } diff --git a/src/Serializer/MetricSerializerFactory.php b/src/Serializer/MetricSerializerFactory.php index adb5123..44a4593 100644 --- a/src/Serializer/MetricSerializerFactory.php +++ b/src/Serializer/MetricSerializerFactory.php @@ -1,10 +1,13 @@ $labels * The labels. * * @return string * The labels in prometheus format. */ - protected function encodeLabels(array $labels) { + protected function encodeLabels(array $labels): string { if (empty($labels)) { return ''; } @@ -55,16 +59,17 @@ protected function encodeLabels(array $labels) { /** * Escape special characters in values. * - * @param string $value + * @param string|int|float $value * The raw value. * * @return string * The escaped value. */ - protected function escapeValue($value) { + protected function escapeValue(string|int|float $value): string { + $value = (string) $value; + $value = str_replace("\\", "\\\\", $value); $value = str_replace("\"", "\\\"", $value); $value = str_replace("\n", "\\n", $value); - $value = str_replace("\\", "\\\\", $value); return $value; } diff --git a/src/Summary.php b/src/Summary.php index 3d3e2c7..50791a4 100644 --- a/src/Summary.php +++ b/src/Summary.php @@ -1,5 +1,7 @@ sum = 0; $this->count = 0; @@ -54,12 +59,15 @@ public function getType(): string { /** * Sets an array of summary values. * - * @param array $buckets + * @param array $buckets * The list of buckets. - * @param array $values + * @param array $values * The list of bucket values. + * + * @throws \InvalidArgumentException + * If the number of buckets and values do not match. */ - public function setValues(array $buckets, array $values) { + public function setValues(array $buckets, array $values): void { if (count($buckets) != count($values)) { throw new \InvalidArgumentException("The number of buckets and values must match."); } @@ -74,7 +82,7 @@ public function setValues(array $buckets, array $values) { /** * {@inheritdoc} */ - public function getLabelledValues() { + public function getLabelledValues(): array { $labelledValues = parent::getLabelledValues(); $labelledValues[] = new LabelledValue($this->getName() . '_sum', $this->sum, []); diff --git a/tests/Unit/CounterTest.php b/tests/Unit/CounterTest.php index dae97c9..405c762 100644 --- a/tests/Unit/CounterTest.php +++ b/tests/Unit/CounterTest.php @@ -1,19 +1,23 @@ set(89, ['baz' => 'wiz']); @@ -29,9 +33,9 @@ public function testCounter() { } /** - * @covers ::__construct + * Tests that invalid counter names throw an exception. */ - public function testInvalidCounter() { + public function testInvalidCounter(): void { $this->expectException(\InvalidArgumentException::class); new Counter('foo^&**', 'bar&*', 'Example counter help'); } diff --git a/tests/Unit/GaugeTest.php b/tests/Unit/GaugeTest.php index 38e39a0..394761a 100644 --- a/tests/Unit/GaugeTest.php +++ b/tests/Unit/GaugeTest.php @@ -1,21 +1,23 @@ set(100, ['baz' => 'wiz']); $gauge->set(90, ['wobble' => 'wibble', 'bing' => 'bong']); @@ -37,11 +39,8 @@ public function testGauge() { /** * Ensure a gauge with no values is valid. - * - * @covers ::__construct - * @covers ::getLabelledValues */ - public function testGaugeNoValues() { + public function testGaugeNoValues(): void { $gauge = new Gauge("foo", "bar", "A test gauge"); $this->assertEmpty($gauge->getLabelledValues()); } diff --git a/tests/Unit/Serializer/MetricSerializerTest.php b/tests/Unit/Serializer/MetricSerializerTest.php index 24af838..0325038 100644 --- a/tests/Unit/Serializer/MetricSerializerTest.php +++ b/tests/Unit/Serializer/MetricSerializerTest.php @@ -1,7 +1,10 @@ getTestGauge(); $gaugeText = $serializer->serialize($gauge, 'prometheus'); @@ -25,9 +29,9 @@ public function testSerializeGauge() { } /** - * @covers ::create + * Tests serializing a counter. */ - public function testSerializeCounter() { + public function testSerializeCounter(): void { $serializer = MetricSerializerFactory::create(); $counter = $this->getTestCounter(); $counterText = $serializer->serialize($counter, 'prometheus'); @@ -36,9 +40,9 @@ public function testSerializeCounter() { } /** - * @covers ::create + * Tests serializing a summary. */ - public function testSerializeSummary() { + public function testSerializeSummary(): void { $serializer = MetricSerializerFactory::create(); $summary = $this->getTestSummary(); $summaryText = $serializer->serialize($summary, 'prometheus'); @@ -52,7 +56,7 @@ public function testSerializeSummary() { * @return \PNX\Prometheus\Gauge * The gauge. */ - protected function getTestGauge() { + protected function getTestGauge(): Gauge { $gauge = new Gauge("foo", "bar", "A test gauge"); $gauge->set(100, ['baz' => 'wiz']); $gauge->set(90, ['wobble' => 'wibble', 'bing' => 'bong']); @@ -66,19 +70,19 @@ protected function getTestGauge() { * @return \PNX\Prometheus\Counter * The counter. */ - protected function getTestCounter() { + protected function getTestCounter(): Counter { $counter = new Counter("foo", "bar", "A counter for testing"); $counter->set(100, ['baz' => 'wiz']); return $counter; } /** - * Gets a counter for testing. + * Gets a summary for testing. * * @return \PNX\Prometheus\Summary * The summary. */ - protected function getTestSummary() { + protected function getTestSummary(): Summary { $summary = new Summary("foo", "bar", "Summary help text", 'baz'); $buckets = [0, 0.25, 0.5, 0.75, 1]; diff --git a/tests/Unit/SummaryTest.php b/tests/Unit/SummaryTest.php index af96eee..b773439 100644 --- a/tests/Unit/SummaryTest.php +++ b/tests/Unit/SummaryTest.php @@ -1,19 +1,23 @@