diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..6867b6a0ff56aa035c48a4b08bc98ab3d1241338 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,142 @@ +################ +# GitLab template for the RabbitMQ module. +# Utilizes the Drupal gitlab_templates base +# +# Additional features: +# * PHPStan analysis. +# * PCOV Code Coverage reporting. +################ + +include: + ################ + # DrupalCI includes: + # As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically. + # View these include files at https://git.drupalcode.org/project/gitlab_templates/ + ################ + - project: $_GITLAB_TEMPLATES_REPO + ref: $_GITLAB_TEMPLATES_REF + file: + - '/includes/include.drupalci.main.yml' + - '/includes/include.drupalci.variables.yml' + - '/includes/include.drupalci.workflows.yml' + +################ +# Pipeline configuration variables +# +# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/1.0.x/includes/include.drupalci.variables.yml +################ +variables: + _TARGET_CORE: + value: "$CORE_SUPPORTED" + _TARGET_PHP: + value: "8.1" + # Collect code coverage data. + _PHPUNIT_EXTRA: + value: --coverage-cobertura $CI_PROJECT_DIR/coverage.xml --coverage-filter $CI_PROJECT_DIR/$_WEB_ROOT/modules/custom/ --coverage-text --colors=never + # Generate a new phpstan baseline. + GENERATE_PHPSTAN_BASELINE: + value: "0" + +################ +# Customize composer stage to not symlink the module as it interferes with +# the PCOV extension tracking coverage. +# +# During archive assets stage trying to overwrite symlinks with actual +# files/directives causes errors so we must perform these actions +# during the initial script phase instead of during after_script. +################ +.composer-local: + extends: .composer-base + script: + # Run the normal commands first. + - !reference [.composer-base, script] + # Remove the module directory and all the symlinks in it. + - rm -Rf web/modules/custom/$CI_PROJECT_NAME + # Duplicate the code into the modules folder without using symlinks. + - git clone ./ web/modules/custom/$CI_PROJECT_NAME + # Remove the build root copy to avoid duplicate classnames. + - rm -Rf src tests + +composer: + extends: .composer-local + +################ +# Add PHPStan validation. +################ +phpstan: + stage: validate + needs: ["composer"] + script: + # Output a copy in GitLab code quality format. + - vendor/bin/phpstan analyse -c web/modules/custom/$CI_PROJECT_NAME/phpstan.neon --no-progress --error-format gitlab web/modules/custom/$CI_PROJECT_NAME > phpstan.xml || true + # Output a copy in junit + - vendor/bin/phpstan analyse -c web/modules/custom/$CI_PROJECT_NAME/phpstan.neon --no-progress --error-format junit web/modules/custom/$CI_PROJECT_NAME > junit.xml || true + # Ensure paths for coverage are git-relative. + - sed -i "s#web/modules/custom/$CI_PROJECT_NAME/##" phpstan.xml junit.xml || true + # Output a copy in plain text for human logs. + - vendor/bin/phpstan analyse -c web/modules/custom/$CI_PROJECT_NAME/phpstan.neon --no-progress --error-format table web/modules/custom/$CI_PROJECT_NAME + allow_failure: true + artifacts: + expose_as: phpstan + when: always + expire_in: 6 mos + reports: + codequality: phpstan.xml + junit: junit.xml + paths: + - phpstan.xml + - junit.xml + +phpstan-new-baseline: + stage: validate + rules: + - if: '$GENERATE_PHPSTAN_BASELINE == "1"' + when: always + - when: never + script: + # Purge the existing baseline. + - echo '' > web/modules/custom/$CI_PROJECT_NAME/phpstan-baseline.neon + # Generate a new baseline. + # We must put the baseline in the source folder to get relative paths. + - vendor/bin/phpstan analyse -c web/modules/custom/$CI_PROJECT_NAME/phpstan.neon --no-progress --generate-baseline web/modules/custom/$CI_PROJECT_NAME/phpstan-baseline-new.neon web/modules/custom/$CI_PROJECT_NAME + - mv web/modules/custom/$CI_PROJECT_NAME/phpstan-baseline-new.neon phpstan-baseline-new.neon + allow_failure: false + artifacts: + expose_as: phpstan-new-baseline + when: always + expire_in: 6 mos + paths: + - phpstan-baseline-new.neon + +################ +# Customize phpunit stage to: +# * Enable PCOV Coverage Reporting. +# * Collect code coverage data. +################ +phpunit: + extends: .phpunit-base + services: + # Include the normal containers. + - !reference [.with-database] + - !reference [.with-chrome] + # Add a container for RabbitMq. + - name: rabbitmq:3 + before_script: + # Install and enable PCOV. + - echo -e '[pcov]\npcov.directory=.' > /usr/local/etc/php/conf.d/pcov.ini + - pecl install pcov && docker-php-ext-enable pcov + after_script: + # Ensure paths for coverage are git-relative. + - sed -i "s#<source>$CI_PROJECT_DIR/web/modules/custom/$CI_PROJECT_NAME#<source>$CI_PROJECT_DIR#" coverage.xml + # Capture line coverage counts for GitLab UI display. + coverage: /^\s*Lines:\s*\d+.\d+\%/ + artifacts: + reports: + coverage_report: + coverage_format: cobertura + path: coverage.xml + paths: + - junit.xml + - apache.access.log.txt + - $BROWSERTEST_OUTPUT_DIRECTORY + - coverage.xml diff --git a/composer.json b/composer.json index 08924a0c4c67ee5e787064dbefb174fbec117c6b..792b2fd104f9f9d96d445b4b203d2c18d17331a1 100644 --- a/composer.json +++ b/composer.json @@ -10,10 +10,18 @@ "homepage": "https://www.drupal.org/project/rabbitmq", "license": "GPL-2.0-or-later", "name": "drupal/rabbitmq", + "prefer-stable": true, "require": { "drupal/core": "^9 || ^10", "php-amqplib/php-amqplib": "^3.1" }, + "require-dev": { + "mglaman/phpstan-drupal": "^1.1", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3" + }, "suggest": { "ext-pcntl": "Needed to support the timeout option" }, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000000000000000000000000000000000000..50f460872f23224527fbd5913a8bae94d5c99580 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,360 @@ +parameters: + ignoreErrors: + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 2 + path: rabbitmq.install + + - + message: "#^Cannot access offset 'name' on mixed\\.$#" + count: 4 + path: rabbitmq.install + + - + message: "#^Function rabbitmq_update_9401\\(\\) has no return type specified\\.$#" + count: 1 + path: rabbitmq.install + + - + message: "#^Cannot access offset string on mixed\\.$#" + count: 1 + path: src/Commands/RabbitmqCommands.php + + - + message: "#^Cannot cast mixed to int\\.$#" + count: 1 + path: src/Commands/RabbitmqCommands.php + + - + message: "#^Method Drupal\\\\rabbitmq\\\\Commands\\\\RabbitmqCommands\\:\\:worker\\(\\) should return int but return statement is missing\\.$#" + count: 1 + path: src/Commands/RabbitmqCommands.php + + - + message: "#^Parameter \\#2 \\$length of method Drush\\\\Commands\\\\DrushCommands\\:\\:yell\\(\\) expects int, null given\\.$#" + count: 1 + path: src/Commands/RabbitmqCommands.php + + - + message: "#^Cannot access offset mixed on mixed\\.$#" + count: 1 + path: src/ConnectionFactory.php + + - + message: "#^Method Drupal\\\\rabbitmq\\\\ConnectionFactory\\:\\:getCredentials\\(\\) should return array but returns mixed\\.$#" + count: 1 + path: src/ConnectionFactory.php + + - + message: "#^Parameter \\#2 \\$array of function array_key_exists expects array, mixed given\\.$#" + count: 1 + path: src/ConnectionFactory.php + + - + message: "#^Access to an undefined property object\\:\\:\\$data\\.$#" + count: 1 + path: src/Consumer.php + + - + message: "#^Access to an undefined property object\\:\\:\\$id\\.$#" + count: 2 + path: src/Consumer.php + + - + message: """ + #^Call to deprecated function watchdog_exception\\(\\)\\: + in drupal\\:10\\.1\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use + Use \\\\Drupal\\\\Core\\\\Utility\\\\Error\\:\\:logException\\(\\) instead\\.$# + """ + count: 1 + path: src/Consumer.php + + - + message: "#^Cannot call method basic_ack\\(\\) on PhpAmqpLib\\\\Channel\\\\AMQPChannel\\|null\\.$#" + count: 1 + path: src/Consumer.php + + - + message: "#^Cannot call method basic_reject\\(\\) on PhpAmqpLib\\\\Channel\\\\AMQPChannel\\|null\\.$#" + count: 1 + path: src/Consumer.php + + - + message: "#^Parameter \\#1 \\$data of function unserialize expects string, mixed given\\.$#" + count: 1 + path: src/Consumer.php + + - + message: "#^Parameter \\#1 \\$maxIterations of method Drupal\\\\rabbitmq\\\\Consumer\\:\\:hitIterationsLimit\\(\\) expects int, mixed given\\.$#" + count: 2 + path: src/Consumer.php + + - + message: "#^Parameter \\#1 \\$memoryLimit of method Drupal\\\\rabbitmq\\\\Consumer\\:\\:hitMemoryLimit\\(\\) expects int, mixed given\\.$#" + count: 3 + path: src/Consumer.php + + - + message: "#^Parameter \\#1 \\$seconds of function pcntl_alarm expects int, mixed given\\.$#" + count: 2 + path: src/Consumer.php + + - + message: "#^Parameter \\#3 \\$timeout of method Drupal\\\\rabbitmq\\\\Consumer\\:\\:getCallback\\(\\) expects int, mixed given\\.$#" + count: 1 + path: src/Consumer.php + + - + message: "#^Parameter \\#3 \\$timeout of method PhpAmqpLib\\\\Channel\\\\AbstractChannel\\:\\:wait\\(\\) expects float\\|int\\|null, mixed given\\.$#" + count: 1 + path: src/Consumer.php + + - + message: "#^Cannot call method getServerProperties\\(\\) on PhpAmqpLib\\\\Connection\\\\AbstractConnection\\|null\\.$#" + count: 1 + path: src/Controller/StatusController.php + + - + message: "#^\\\\Drupal calls should be avoided in classes, use dependency injection instead$#" + count: 1 + path: src/Controller/StatusController.php + + - + message: "#^Access to an undefined property object\\:\\:\\$id\\.$#" + count: 5 + path: src/Queue/Queue.php + + - + message: "#^Method Drupal\\\\rabbitmq\\\\Queue\\\\Queue\\:\\:createItem\\(\\) has parameter \\$data with no type specified\\.$#" + count: 1 + path: src/Queue/Queue.php + + - + message: "#^Method Drupal\\\\rabbitmq\\\\Queue\\\\Queue\\:\\:createItem\\(\\) should return bool but returns string\\|false\\.$#" + count: 1 + path: src/Queue/Queue.php + + - + message: "#^Method Drupal\\\\rabbitmq\\\\Queue\\\\Queue\\:\\:createQueue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/Queue/Queue.php + + - + message: "#^Parameter \\#1 \\$array of function array_slice expects array, mixed given\\.$#" + count: 1 + path: src/Queue/Queue.php + + - + message: "#^\\\\Drupal calls should be avoided in classes, use dependency injection instead$#" + count: 2 + path: src/Queue/Queue.php + + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 2 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot access offset 'auto_delete' on mixed\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot access offset 'durable' on mixed\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot access offset 'internal' on mixed\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot access offset 'name' on mixed\\.$#" + count: 4 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot access offset 'nowait' on mixed\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot access offset 'passive' on mixed\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot access offset 'type' on mixed\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot call method channel\\(\\) on PhpAmqpLib\\\\Connection\\\\AbstractConnection\\|null\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot call method queue_bind\\(\\) on PhpAmqpLib\\\\Channel\\\\AMQPChannel\\|null\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Dead catch \\- PhpAmqpLib\\\\Exception\\\\AMQPProtocolChannelException is never thrown in the try block\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Parameter \\#1 \\$exchange of method PhpAmqpLib\\\\Channel\\\\AMQPChannel\\:\\:exchange_declare\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Parameter \\#2 \\$type of method PhpAmqpLib\\\\Channel\\\\AMQPChannel\\:\\:exchange_declare\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Parameter \\#3 \\$passive of method PhpAmqpLib\\\\Channel\\\\AMQPChannel\\:\\:exchange_declare\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Parameter \\#4 \\$durable of method PhpAmqpLib\\\\Channel\\\\AMQPChannel\\:\\:exchange_declare\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Parameter \\#5 \\$auto_delete of method PhpAmqpLib\\\\Channel\\\\AMQPChannel\\:\\:exchange_declare\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Parameter \\#6 \\$internal of method PhpAmqpLib\\\\Channel\\\\AMQPChannel\\:\\:exchange_declare\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Parameter \\#7 \\$nowait of method PhpAmqpLib\\\\Channel\\\\AMQPChannel\\:\\:exchange_declare\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/Queue/QueueBase.php + + - + message: "#^Cannot access offset 'queue_default' on mixed\\.$#" + count: 1 + path: src/Queue/QueueFactory.php + + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/RabbitmqServiceProvider.php + + - + message: "#^Access to an undefined property object\\:\\:\\$data\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Cannot access an offset on mixed\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Cannot access offset string on mixed\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Cannot access property \\$created on bool\\|object\\.$#" + count: 2 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Cannot access property \\$data on bool\\|object\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Cannot access property \\$item_id on bool\\|object\\.$#" + count: 2 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Kernel\\\\RabbitMqQueueBaseTest\\:\\:alter\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Kernel\\\\RabbitMqQueueBaseTest\\:\\:testCreateAndClaimObjects\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Kernel\\\\RabbitMqQueueBaseTest\\:\\:testItemId\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Kernel\\\\RabbitMqQueueBaseTest\\:\\:testMissingQueueNameException\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Kernel\\\\RabbitMqQueueBaseTest\\:\\:testQueueConfigSave\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Kernel\\\\RabbitMqQueueBaseTest\\:\\:testQueuePriority\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Kernel\\\\RabbitMqQueueBaseTest\\:\\:testTimestamp\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqQueueBaseTest.php + + - + message: "#^Cannot access an offset on mixed\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqTestBase.php + + - + message: "#^Cannot use array destructuring on array\\|null\\.$#" + count: 1 + path: tests/src/Kernel/RabbitMqTestBase.php + + - + message: "#^Access to an undefined property object\\:\\:\\$created\\.$#" + count: 1 + path: tests/src/Unit/QueueTest.php + + - + message: "#^Access to an undefined property object\\:\\:\\$item_id\\.$#" + count: 1 + path: tests/src/Unit/QueueTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Unit\\\\QueueTest\\:\\:testClaimItemTimestamp\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Unit/QueueTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Unit\\\\QueueTest\\:\\:testCreateItemId\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Unit/QueueTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Unit\\\\QueueTest\\:\\:testCreateItemTimestamp\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Unit/QueueTest.php + + - + message: "#^Method Drupal\\\\Tests\\\\rabbitmq\\\\Unit\\\\QueueTest\\:\\:testItemIdFromClaimItem\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/src/Unit/QueueTest.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000000000000000000000000000000000000..19661a3c79bc3e93912fdc5a4596f2e1e9533637 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,18 @@ +parameters: + fileExtensions: + - php + - module + - install + paths: + - ./ + excludePaths: + - web + - vendor + - expand_composer_json.php + - symlink_project.php + level: 9 + ignoreErrors: + - '#Unsafe usage of new static\(\)#' + checkMissingIterableValueType: false +includes: + - phpstan-baseline.neon \ No newline at end of file