Add extension template with PHPStan, PHPUnit and phpcs

This commit is contained in:
Jens Schuppe 2024-03-25 14:51:55 +01:00
parent b4c6581d4f
commit 8cd928caa9
22 changed files with 825 additions and 1 deletions

240
.editorconfig Normal file
View file

@ -0,0 +1,240 @@
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 2
ij_continuation_indent_size = 2
ij_visual_guides = 80,120
[{*.php}]
ij_php_align_assignments = false
ij_php_align_class_constants = false
ij_php_align_enum_cases = false
ij_php_align_group_field_declarations = false
ij_php_align_inline_comments = false
ij_php_align_key_value_pairs = false
ij_php_align_match_arm_bodies = false
ij_php_align_multiline_array_initializer_expression = false
ij_php_align_multiline_binary_operation = false
ij_php_align_multiline_chained_methods = false
ij_php_align_multiline_extends_list = false
ij_php_align_multiline_for = true
ij_php_align_multiline_parameters = false
ij_php_align_multiline_parameters_in_calls = false
ij_php_align_multiline_ternary_operation = false
ij_php_align_named_arguments = false
ij_php_align_phpdoc_comments = false
ij_php_align_phpdoc_param_names = false
ij_php_anonymous_brace_style = end_of_line
ij_php_api_weight = 28
ij_php_array_initializer_new_line_after_left_brace = true
ij_php_array_initializer_right_brace_on_new_line = true
ij_php_array_initializer_wrap = on_every_item
ij_php_assignment_wrap = normal
ij_php_attributes_wrap = normal
ij_php_author_weight = 28
ij_php_binary_operation_sign_on_next_line = false
ij_php_binary_operation_wrap = normal
ij_php_blank_lines_after_class_header = 1
ij_php_blank_lines_after_function = 1
ij_php_blank_lines_after_imports = 1
ij_php_blank_lines_after_opening_tag = 0
ij_php_blank_lines_after_package = 1
ij_php_blank_lines_around_class = 1
ij_php_blank_lines_around_constants = 1
ij_php_blank_lines_around_enum_cases = 0
ij_php_blank_lines_around_field = 1
ij_php_blank_lines_around_method = 1
ij_php_blank_lines_before_class_end = 1
ij_php_blank_lines_before_imports = 1
ij_php_blank_lines_before_method_body = 0
ij_php_blank_lines_before_package = 1
ij_php_blank_lines_before_return_statement = 1
ij_php_blank_lines_between_imports = 0
ij_php_block_brace_style = end_of_line
ij_php_call_parameters_new_line_after_left_paren = true
ij_php_call_parameters_right_paren_on_new_line = true
ij_php_call_parameters_wrap = on_every_item
ij_php_catch_on_new_line = true
ij_php_category_weight = 28
ij_php_class_brace_style = end_of_line
ij_php_comma_after_last_argument = false
ij_php_comma_after_last_array_element = true
ij_php_comma_after_last_closure_use_var = false
ij_php_comma_after_last_match_arm = false
ij_php_comma_after_last_parameter = false
ij_php_concat_spaces = true
ij_php_copyright_weight = 28
ij_php_deprecated_weight = 4
ij_php_do_while_brace_force = always
ij_php_else_if_style = as_is
ij_php_else_on_new_line = true
ij_php_example_weight = 28
ij_php_extends_keyword_wrap = off
ij_php_extends_list_wrap = off
ij_php_fields_default_visibility = private
ij_php_filesource_weight = 28
ij_php_finally_on_new_line = true
ij_php_for_brace_force = always
ij_php_for_statement_new_line_after_left_paren = false
ij_php_for_statement_right_paren_on_new_line = false
ij_php_for_statement_wrap = off
ij_php_force_empty_methods_in_one_line = false
ij_php_force_short_declaration_array_style = true
ij_php_getters_setters_naming_style = camel_case
ij_php_getters_setters_order_style = getters_first
ij_php_global_weight = 28
ij_php_group_use_wrap = on_every_item
ij_php_if_brace_force = always
ij_php_if_lparen_on_next_line = false
ij_php_if_rparen_on_next_line = false
ij_php_ignore_weight = 28
ij_php_import_sorting = alphabetic
ij_php_indent_break_from_case = true
ij_php_indent_case_from_switch = true
ij_php_indent_code_in_php_tags = false
ij_php_internal_weight = 28
ij_php_keep_blank_lines_after_lbrace = 1
ij_php_keep_blank_lines_before_right_brace = 1
ij_php_keep_blank_lines_in_code = 1
ij_php_keep_blank_lines_in_declarations = 1
ij_php_keep_control_statement_in_one_line = false
ij_php_keep_first_column_comment = false
ij_php_keep_indents_on_empty_lines = false
ij_php_keep_line_breaks = false
ij_php_keep_rparen_and_lbrace_on_one_line = true
ij_php_keep_simple_classes_in_one_line = false
ij_php_keep_simple_methods_in_one_line = false
ij_php_lambda_brace_style = end_of_line
ij_php_license_weight = 28
ij_php_line_comment_add_space = false
ij_php_line_comment_at_first_column = true
ij_php_link_weight = 28
ij_php_lower_case_boolean_const = false
ij_php_lower_case_keywords = true
ij_php_lower_case_null_const = false
ij_php_method_brace_style = end_of_line
ij_php_method_call_chain_wrap = on_every_item
ij_php_method_parameters_new_line_after_left_paren = true
ij_php_method_parameters_right_paren_on_new_line = true
ij_php_method_parameters_wrap = on_every_item
ij_php_method_weight = 28
ij_php_modifier_list_wrap = false
ij_php_multiline_chained_calls_semicolon_on_new_line = true
ij_php_namespace_brace_style = 1
ij_php_new_line_after_php_opening_tag = true
ij_php_null_type_position = in_the_end
ij_php_package_weight = 28
ij_php_param_weight = 1
ij_php_parameters_attributes_wrap = normal
ij_php_parentheses_expression_new_line_after_left_paren = false
ij_php_parentheses_expression_right_paren_on_new_line = false
ij_php_phpdoc_blank_line_before_tags = true
ij_php_phpdoc_blank_lines_around_parameters = true
ij_php_phpdoc_keep_blank_lines = true
ij_php_phpdoc_param_spaces_between_name_and_description = 1
ij_php_phpdoc_param_spaces_between_tag_and_type = 1
ij_php_phpdoc_param_spaces_between_type_and_name = 1
ij_php_phpdoc_use_fqcn = true
ij_php_phpdoc_wrap_long_lines = true
ij_php_place_assignment_sign_on_next_line = false
ij_php_place_parens_for_constructor = 1
ij_php_property_read_weight = 28
ij_php_property_weight = 28
ij_php_property_write_weight = 28
ij_php_return_type_on_new_line = false
ij_php_return_weight = 2
ij_php_see_weight = 5
ij_php_since_weight = 28
ij_php_sort_phpdoc_elements = true
ij_php_space_after_colon = true
ij_php_space_after_colon_in_enum_backed_type = true
ij_php_space_after_colon_in_named_argument = true
ij_php_space_after_colon_in_return_type = true
ij_php_space_after_comma = true
ij_php_space_after_for_semicolon = true
ij_php_space_after_quest = true
ij_php_space_after_type_cast = true
ij_php_space_after_unary_not = false
ij_php_space_before_array_initializer_left_brace = false
ij_php_space_before_catch_keyword = true
ij_php_space_before_catch_left_brace = true
ij_php_space_before_catch_parentheses = true
ij_php_space_before_class_left_brace = true
ij_php_space_before_closure_left_parenthesis = true
ij_php_space_before_colon = true
ij_php_space_before_colon_in_enum_backed_type = false
ij_php_space_before_colon_in_named_argument = false
ij_php_space_before_colon_in_return_type = false
ij_php_space_before_comma = false
ij_php_space_before_do_left_brace = true
ij_php_space_before_else_keyword = true
ij_php_space_before_else_left_brace = true
ij_php_space_before_finally_keyword = true
ij_php_space_before_finally_left_brace = true
ij_php_space_before_for_left_brace = true
ij_php_space_before_for_parentheses = true
ij_php_space_before_for_semicolon = false
ij_php_space_before_if_left_brace = true
ij_php_space_before_if_parentheses = true
ij_php_space_before_method_call_parentheses = false
ij_php_space_before_method_left_brace = true
ij_php_space_before_method_parentheses = false
ij_php_space_before_quest = true
ij_php_space_before_short_closure_left_parenthesis = false
ij_php_space_before_switch_left_brace = true
ij_php_space_before_switch_parentheses = true
ij_php_space_before_try_left_brace = true
ij_php_space_before_unary_not = false
ij_php_space_before_while_keyword = true
ij_php_space_before_while_left_brace = true
ij_php_space_before_while_parentheses = true
ij_php_space_between_ternary_quest_and_colon = false
ij_php_spaces_around_additive_operators = true
ij_php_spaces_around_arrow = false
ij_php_spaces_around_assignment_in_declare = true
ij_php_spaces_around_assignment_operators = true
ij_php_spaces_around_bitwise_operators = true
ij_php_spaces_around_equality_operators = true
ij_php_spaces_around_logical_operators = true
ij_php_spaces_around_multiplicative_operators = true
ij_php_spaces_around_null_coalesce_operator = true
ij_php_spaces_around_pipe_in_union_type = false
ij_php_spaces_around_relational_operators = true
ij_php_spaces_around_shift_operators = true
ij_php_spaces_around_unary_operator = false
ij_php_spaces_around_var_within_brackets = false
ij_php_spaces_within_array_initializer_braces = false
ij_php_spaces_within_brackets = false
ij_php_spaces_within_catch_parentheses = false
ij_php_spaces_within_for_parentheses = false
ij_php_spaces_within_if_parentheses = false
ij_php_spaces_within_method_call_parentheses = false
ij_php_spaces_within_method_parentheses = false
ij_php_spaces_within_parentheses = false
ij_php_spaces_within_short_echo_tags = true
ij_php_spaces_within_switch_parentheses = false
ij_php_spaces_within_while_parentheses = false
ij_php_special_else_if_treatment = false
ij_php_subpackage_weight = 28
ij_php_ternary_operation_signs_on_next_line = true
ij_php_ternary_operation_wrap = on_every_item
ij_php_throws_weight = 3
ij_php_todo_weight = 6
ij_php_treat_multiline_arrays_and_lambdas_multiline = false
ij_php_unknown_tag_weight = 28
ij_php_upper_case_boolean_const = true
ij_php_upper_case_null_const = true
ij_php_uses_weight = 28
ij_php_var_weight = 0
ij_php_variable_naming_style = camel_case
ij_php_version_weight = 28
ij_php_while_brace_force = always
ij_php_while_on_new_line = false
[{*.neon,*.neon.dist,*neon.template}]
indent_style = tab
tab_width = 4

42
.github/workflows/phpcs.yml vendored Normal file
View file

@ -0,0 +1,42 @@
name: PHP_CodeSniffer
on:
pull_request:
paths:
- '**.php'
- tools/phpcs/composer.json
- phpcs.xml.dist
jobs:
phpcs:
runs-on: ubuntu-latest
name: PHP_CodeSniffer
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
coverage: none
tools: cs2pr
env:
fail-fast: true
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('tools/phpcs/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer composer-phpcs -- update --no-progress --prefer-dist
- name: Run PHP_CodeSniffer
run: composer phpcs -- -q --report=checkstyle | cs2pr

52
.github/workflows/phpstan.yml vendored Normal file
View file

@ -0,0 +1,52 @@
name: PHPStan
on:
pull_request:
paths:
- '**.php'
- composer.json
- tools/phpstan/composer.json
- ci/composer.json
- phpstan.ci.neon
- phpstan.neon.dist
jobs:
phpstan:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ['7.4', '8.0', '8.1']
prefer: ['prefer-stable', 'prefer-lowest']
name: PHPStan with PHP ${{ matrix.php-versions }} ${{ matrix.prefer }}
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
coverage: none
env:
fail-fast: true
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.prefer }}-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-${{ matrix.prefer }}-
- name: Install dependencies
run: |
composer update --no-progress --prefer-dist --${{ matrix.prefer }} &&
composer composer-phpunit -- update --no-progress --prefer-dist &&
composer composer-phpstan -- update --no-progress --prefer-dist --optimize-autoloader &&
composer --working-dir=ci update --no-progress --prefer-dist --${{ matrix.prefer }} --optimize-autoloader --ignore-platform-req=ext-gd
- name: Run PHPStan
run: composer phpstan -- analyse -c phpstan.ci.neon

37
.github/workflows/phpunit.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: PHPUnit
on:
pull_request:
paths:
- '**.php'
- composer.json
- tools/phpunit/composer.json
- phpunit.xml.dist
- tests/docker-prepare.sh
env:
# On github CI machine creating the "/vendor" volume fails otherwise with: read-only file system: unknown
BIND_VOLUME_PERMISSIONS: rw
jobs:
phpunit:
runs-on: ubuntu-latest
strategy:
matrix:
civicrm-image-tags: [ '5-drupal-php8.1', '5-drupal-php7.4', '5.56-drupal-php7.4' ]
name: PHPUnit with Docker image michaelmcandrew/civicrm:${{ matrix.civicrm-image-tags }}
env:
CIVICRM_IMAGE_TAG: ${{ matrix.civicrm-image-tags }}
steps:
- uses: actions/checkout@v3
- name: Pull images
run: docker compose -f tests/docker-compose.yml pull --quiet
- name: Start containers
run: docker compose -f tests/docker-compose.yml up -d
- name: Prepare environment
run: docker compose -f tests/docker-compose.yml exec civicrm sites/default/files/civicrm/ext/de.systopia.twingle/tests/docker-prepare.sh
- name: Run PHPUnit
run: docker compose -f tests/docker-compose.yml exec civicrm sites/default/files/civicrm/ext/de.systopia.twingle/tests/docker-phpunit.sh
- name: Remove containers
run: docker compose -f tests/docker-compose.yml down -v

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
/.phpcs.cache
/.phpunit.result.cache
/.phpstan/
/ci/composer.lock
/ci/vendor/
/composer.lock
/phpstan.neon
/tools/*/vendor/
/tools/*/composer.lock
/vendor/

2
ci/README.md Normal file
View file

@ -0,0 +1,2 @@
The dependencies specified in composer.json of this directory are required to
run phpstan in CI.

33
ci/composer.json Normal file
View file

@ -0,0 +1,33 @@
{
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"allow-plugins": {
"civicrm/composer-compile-plugin": false,
"civicrm/composer-downloads-plugin": true,
"cweagans/composer-patches": true
}
},
"require": {
"civicrm/civicrm-core": "^5"
},
"scripts": {
"post-install-or-update": [
"# The following statements are only necessary when the extension is inside a CiviCRM installation, actually not required in CI.",
"# Avoid redeclaration of function \\GuzzleHttp\\http_build_query()",
"if [ -e vendor/civicrm/civicrm-core/guzzle_php81_shim.php ]; then echo '' >vendor/civicrm/civicrm-core/guzzle_php81_shim.php; fi",
"# Avoid redeclaration of function \\GuzzleHttp\\Promise\\queue()",
"if [ -e vendor/guzzlehttp/promises/src/functions.php ]; then echo '' >vendor/guzzlehttp/promises/src/functions.php; fi",
"# Avoid CiviCRM load extensions in vendor",
"if [ -e vendor/civicrm ]; then find vendor/civicrm -name 'info.xml' -delete; fi",
"# Avoid Class 'CRM_AfformAdmin_ExtensionUtil' not found",
"find vendor -name '*.mgd.php' -delete"
],
"post-install-cmd": [
"@post-install-or-update"
],
"post-update-cmd": [
"@post-install-or-update"
]
}
}

45
composer.json Normal file
View file

@ -0,0 +1,45 @@
{
"name": "systopia/de.systopia.twingle",
"type": "civicrm-ext",
"license": "AGPL-3.0-or-later",
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true
},
"require": {
},
"scripts": {
"composer-phpcs": [
"@composer --working-dir=tools/phpcs"
],
"composer-phpstan": [
"@composer --working-dir=tools/phpstan"
],
"composer-phpunit": [
"@composer --working-dir=tools/phpunit"
],
"composer-tools": [
"@composer-phpcs",
"@composer-phpstan",
"@composer-phpunit"
],
"phpcs": [
"@php tools/phpcs/vendor/bin/phpcs"
],
"phpcbf": [
"@php tools/phpcs/vendor/bin/phpcbf"
],
"phpstan": [
"@php tools/phpstan/vendor/bin/phpstan"
],
"phpunit": [
"@php tools/phpunit/vendor/bin/simple-phpunit --coverage-text"
],
"test": [
"@phpcs",
"@phpstan",
"@phpunit"
]
}
}

View file

@ -18,7 +18,7 @@
<version>1.5-dev</version>
<develStage>dev</develStage>
<compatibility>
<ver>5.19</ver>
<ver>5.56</ver>
</compatibility>
<comments></comments>
<requires>

77
phpcs.xml.dist Normal file
View file

@ -0,0 +1,77 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="CiviCRM - Modified"
xsi:noNamespaceSchemaLocation="tools/phpcs/vendor/squizlabs/php_codesniffer/phpcs.xsd">
<description>CiviCRM coding standard with some additional changes</description>
<file>api</file>
<file>Civi</file>
<file>CRM</file>
<file>tests</file>
<exclude-pattern>/CRM/[^/]+/DAO/.*\.php$</exclude-pattern>
<arg name="extensions" value="php"/>
<arg name="cache" value=".phpcs.cache"/>
<arg name="colors"/>
<arg value="sp"/>
<!-- Exit with code 0 if warnings, but no error occurred -->
<config name="ignore_warnings_on_exit" value="true"/>
<rule ref="tools/phpcs/vendor/drupal/coder/coder_sniffer/Drupal">
<!-- Don't enforce phpdoc type hint because it (might) only duplicate a PHP type hint -->
<exclude name="Drupal.Commenting.VariableComment.MissingVar"/>
<!-- Don't enforce phpdoc type hint because it (might) only duplicate a PHP type hint -->
<exclude name="Drupal.Commenting.FunctionComment.ParamMissingDefinition"/>
<!-- False positive when license header is set and variable has no comment -->
<exclude name="Drupal.Commenting.VariableComment.WrongStyle"/>
</rule>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Generic.CodeAnalysis.EmptyStatement">
<exclude name="Generic.CodeAnalysis.EmptyStatement.DetectedCatch"/>
</rule>
<rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall"/>
<rule ref="Generic.Files.OneClassPerFile"/>
<rule ref="Generic.Files.OneInterfacePerFile"/>
<rule ref="Generic.Files.OneObjectStructurePerFile"/>
<rule ref="Generic.Files.OneTraitPerFile"/>
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
<rule ref="Generic.Metrics.CyclomaticComplexity"/>
<rule ref="Generic.Metrics.NestingLevel"/>
<rule ref="Generic.NamingConventions.AbstractClassNamePrefix"/>
<rule ref="Generic.NamingConventions.InterfaceNameSuffix"/>
<rule ref="Generic.NamingConventions.TraitNameSuffix"/>
<rule ref="Generic.PHP.RequireStrictTypes"/>
<rule ref="PSR1.Files.SideEffects"/>
<rule ref="PSR12.Classes.ClassInstantiation"/>
<rule ref="PSR12.Properties.ConstantVisibility"/>
<rule ref="Squiz.PHP.CommentedOutCode"/>
<rule ref="Squiz.PHP.GlobalKeyword"/>
<rule ref="Squiz.Strings.DoubleQuoteUsage">
<exclude name="Squiz.Strings.DoubleQuoteUsage.ContainsVar"/>
</rule>
<!-- Lines can be 120 chars long, but never show errors -->
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="120"/>
<property name="absoluteLineLimit" value="0"/>
</properties>
</rule>
<!-- Ban some functions -->
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array">
<element key="sizeof" value="count"/>
<element key="delete" value="unset"/>
<element key="print" value="echo"/>
<element key="is_null" value="null"/>
<element key="create_function" value="null"/>
</property>
</properties>
</rule>
</ruleset>

13
phpstan.ci.neon Normal file
View file

@ -0,0 +1,13 @@
includes:
- phpstan.neon.dist
parameters:
scanDirectories:
- ci/vendor/civicrm/civicrm-core/CRM/
bootstrapFiles:
- ci/vendor/autoload.php
# Because we test with different versions in CI we have unmatched errors
reportUnmatchedIgnoredErrors: false
ignoreErrors:
# Errors we get when using "prefer-lowest"
- '#::getSubscribedEvents\(\) return type has no value type specified in iterable type array.$#'

42
phpstan.neon.dist Normal file
View file

@ -0,0 +1,42 @@
parameters:
paths:
- api
- Civi
- CRM
- tests
excludePaths:
analyse:
- CRM/*/DAO/*
- tests/phpunit/bootstrap.php
scanFiles:
- twingle.civix.php
- tools/phpunit/vendor/bin/.phpunit/phpunit/src/Framework/TestCase.php
scanDirectories:
- tools/phpunit/vendor/bin/.phpunit/phpunit/src/Framework
bootstrapFiles:
- tools/phpunit/vendor/bin/.phpunit/phpunit/vendor/autoload.php
- vendor/autoload.php
- phpstanBootstrap.php
level: 9
universalObjectCratesClasses:
- Civi\Core\Event\GenericHookEvent
checkTooWideReturnTypesInProtectedAndPublicMethods: true
checkUninitializedProperties: true
checkMissingCallableSignature: true
treatPhpDocTypesAsCertain: false
exceptions:
check:
missingCheckedExceptionInThrows: true
tooWideThrowType: true
checkedExceptionClasses:
- \Webmozart\Assert\InvalidArgumentException
implicitThrows: false
ignoreErrors:
# Note paths are prefixed with "*/" to work with inspections in PHPStorm because of:
# https://youtrack.jetbrains.com/issue/WI-63891/PHPStan-ignoreErrors-configuration-isnt-working-with-inspections
# Example
#- # Accessing results of API requests
#message: "#^Offset '[^']+' does not exist on array[^\\|]+\\|null.$#"
#path: */tests/phpunit/**/*Test.php
tmpDir: .phpstan

13
phpstan.neon.template Normal file
View file

@ -0,0 +1,13 @@
# Copy this file to phpstan.neon and replace {VENDOR_DIR} with the appropriate
# path.
includes:
- phpstan.neon.dist
parameters:
scanDirectories:
- {VENDOR_DIR}/civicrm/civicrm-core/Civi/
- {VENDOR_DIR}/civicrm/civicrm-core/CRM/
- {VENDOR_DIR}/civicrm/civicrm-core/api/
bootstrapFiles:
- {VENDOR_DIR}/autoload.php

43
phpstanBootstrap.php Normal file
View file

@ -0,0 +1,43 @@
<?php
/*
* Copyright (C) 2022 SYSTOPIA GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation in version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types = 1);
// phpcs:disable Drupal.Commenting.DocComment.ContentAfterOpen
/** @var \PHPStan\DependencyInjection\Container $container */
/** @phpstan-var array<string> $bootstrapFiles */
$bootstrapFiles = $container->getParameter('bootstrapFiles');
foreach ($bootstrapFiles as $bootstrapFile) {
if (str_ends_with($bootstrapFile, 'vendor/autoload.php')) {
$vendorDir = dirname($bootstrapFile);
$civiCrmVendorDir = $vendorDir . '/civicrm';
$civiCrmCoreDir = $civiCrmVendorDir . '/civicrm-core';
if (file_exists($civiCrmCoreDir)) {
set_include_path(get_include_path()
. PATH_SEPARATOR . $civiCrmCoreDir
. PATH_SEPARATOR . $civiCrmVendorDir . '/civicrm-packages'
);
// $bootstrapFile might not be included, yet. It is required for the
// following require_once, though.
require_once $bootstrapFile;
// Prevent error "Class 'CRM_Core_Exception' not found in file".
require_once $civiCrmCoreDir . '/CRM/Core/Exception.php';
break;
}
}
}

35
phpunit.xml.dist Normal file
View file

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
colors="true"
failOnRisky="true"
failOnWarning="true"
forceCoversAnnotation="true"
bootstrap="tests/phpunit/bootstrap.php">
<php>
<ini name="error_reporting" value="-1" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[direct]=0&amp;baselineFile=./tests/ignored-deprecations.json"/>
</php>
<testsuites>
<testsuite name="Extension Test Suite">
<directory>./tests/phpunit</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">api</directory>
<directory suffix=".php">CRM</directory>
<directory suffix=".php">Civi</directory>
<exclude>
<directory>CRM/*/DAO</directory>
</exclude>
</whitelist>
</filter>
<listeners>
<listener class="Civi\Test\CiviTestListener">
<arguments/>
</listener>
</listeners>
</phpunit>

33
tests/docker-compose.yml Normal file
View file

@ -0,0 +1,33 @@
version: "3"
services:
civicrm:
image: michaelmcandrew/civicrm:${CIVICRM_IMAGE_TAG:-5-drupal-php8.1}
environment:
- PROJECT_NAME=test
- BASE_URL=http://localhost
- CIVICRM_DB_NAME=test
- CIVICRM_DB_USER=root
- CIVICRM_DB_PASS=secret
- CIVICRM_DB_HOST=mysql
- CIVICRM_DB_PORT=3306
- DRUPAL_DB_NAME=test
- DRUPAL_DB_USER=root
- DRUPAL_DB_PASS=secret
- DRUPAL_DB_HOST=mysql
- DRUPAL_DB_PORT=3306
- PHP_DATE_TIMEZONE=UTC
- DEBUG=ON
- SMTP_HOST=localhost
- SMTP_MAILDOMAIN=example.org
volumes:
- ../:/var/www/html/sites/default/files/civicrm/ext/de.systopia.twingle:${BIND_VOLUME_PERMISSIONS:-ro}
- /var/www/html/sites/default/files/civicrm/ext/de.systopia.twingle/vendor
- /var/www/html/sites/default/files/civicrm/ext/de.systopia.twingle/tools/phpunit/vendor
# Don't start Apache HTTP Server, but keep container running
command: ["tail", "-f", "/dev/null"]
stop_signal: SIGKILL
mysql:
image: mariadb
environment:
MARIADB_ROOT_PASSWORD: secret
MARIADB_DATABASE: test

19
tests/docker-phpunit.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
set -eu -o pipefail
SCRIPT_DIR=$(realpath "$(dirname "$0")")
EXT_DIR=$(dirname "$SCRIPT_DIR")
cd "$EXT_DIR"
if [ ! -e tools/phpunit/vendor/bin ]; then
"$SCRIPT_DIR/docker-prepare.sh"
fi
export XDEBUG_MODE=coverage
# TODO: Remove when not needed, anymore.
# In Docker container with CiviCRM 5.5? all deprecations are reported as direct
# deprecations so "disabling" check of deprecation count is necessary for the
# tests to pass (if baselineFile does not contain all deprecations).
export SYMFONY_DEPRECATIONS_HELPER="max[total]=99999&baselineFile=./tests/ignored-deprecations.json"
composer phpunit -- --cache-result-file=/tmp/.phpunit.result.cache "$@"

45
tests/docker-prepare.sh Executable file
View file

@ -0,0 +1,45 @@
#!/bin/bash
set -eu -o pipefail
EXT_DIR=$(dirname "$(dirname "$(realpath "$0")")")
EXT_NAME=$(basename "$EXT_DIR")
i=0
while ! mysql -h "$CIVICRM_DB_HOST" -P "$CIVICRM_DB_PORT" -u "$CIVICRM_DB_USER" --password="$CIVICRM_DB_PASS" -e 'SELECT 1;' >/dev/null 2>&1; do
i=$((i+1))
if [ $i -gt 10 ]; then
echo "Failed to connect to database" >&2
exit 1
fi
echo -n .
sleep 1
done
echo
export XDEBUG_MODE=off
if mysql -h "$CIVICRM_DB_HOST" -P "$CIVICRM_DB_PORT" -u "$CIVICRM_DB_USER" --password="$CIVICRM_DB_PASS" "$CIVICRM_DB_NAME" -e 'SELECT 1 FROM civicrm_setting LIMIT 1;' >/dev/null 2>&1; then
cv flush
else
# For headless tests it is required that CIVICRM_UF is defined using the corresponding env variable.
sed -E "s/define\('CIVICRM_UF', '([^']+)'\);/define('CIVICRM_UF', getenv('CIVICRM_UF') ?: '\1');/g" \
-i /var/www/html/sites/default/civicrm.settings.php
civicrm-docker-install
# Avoid this error:
# The autoloader expected class "Civi\ActionSchedule\Mapping" to be defined in
# file "[...]/Civi/ActionSchedule/Mapping.php". The file was found but the
# class was not in it, the class name or namespace probably has a typo.
rm -f /var/www/html/sites/all/modules/civicrm/Civi/ActionSchedule/Mapping.php
# For headless tests these files need to exist.
touch /var/www/html/sites/all/modules/civicrm/sql/test_data.mysql
touch /var/www/html/sites/all/modules/civicrm/sql/test_data_second_domain.mysql
cv ext:enable "$EXT_NAME"
fi
cd "$EXT_DIR"
composer update --no-progress --prefer-dist --optimize-autoloader --no-dev
composer composer-phpunit -- update --no-progress --prefer-dist

View file

@ -0,0 +1 @@
[]

11
tools/phpcs/composer.json Normal file
View file

@ -0,0 +1,11 @@
{
"repositories": [
{
"type": "git",
"url": "https://github.com/civicrm/coder.git"
}
],
"require": {
"drupal/coder": "dev-8.x-2.x-civi"
}
}

View file

@ -0,0 +1,18 @@
{
"require": {
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.7",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.1",
"phpstan/phpstan-strict-rules": "^1.2",
"phpstan/phpstan-webmozart-assert": "^1.2",
"thecodingmachine/phpstan-strict-rules": "^1.0",
"voku/phpstan-rules": "^3.0"
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true
},
"sort-packages": true
}
}

View file

@ -0,0 +1,13 @@
{
"require": {
"symfony/phpunit-bridge": "^6.1"
},
"scripts": {
"post-install-cmd": [
"@php vendor/bin/simple-phpunit install"
],
"post-update-cmd": [
"@php vendor/bin/simple-phpunit install"
]
}
}