diff --git a/.dev/docker-compose.tests.yaml b/.dev/docker-compose.tests.yaml index 9558f9d389..4e45a78809 100644 --- a/.dev/docker-compose.tests.yaml +++ b/.dev/docker-compose.tests.yaml @@ -1,12 +1,11 @@ -version: "3.9" - services: leantime-dev: volumes: - - "../:/var/www/html" - - "./xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini" - - "./error_reporting.ini:/usr/local/etc/php/conf.d/error_reporting.ini" - - ${PWD}/test.env:/var/www/html/config/.env + - "${PWD}:/var/www/html" + - "${PWD}/.dev/xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini" + - "${PWD}/.dev/error_reporting.ini:/usr/local/etc/php/conf.d/error_reporting.ini" + - "${PWD}/.dev/test.env:/var/www/html/config/.env" + db: environment: MYSQL_DATABASE: leantime_test diff --git a/.dev/docker-compose.tools.yaml b/.dev/docker-compose.tools.yaml new file mode 100644 index 0000000000..7283f14a56 --- /dev/null +++ b/.dev/docker-compose.tools.yaml @@ -0,0 +1,50 @@ +x-tool-service: &tool-service + profiles: ["tools"] + working_dir: "/app" + volumes: + - "${PWD}:/app" + +services: + composer: + <<: *tool-service + image: composer + container_name: leantime-composer + environment: + COMPOSER_IGNORE_PLATFORM_REQS: 1 + + php: + profiles: ["tools"] + build: . + container_name: leantime-php + working_dir: "/var/www/html" + volumes: + - "${PWD}:/var/www/html" + - "./xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini" + - "./error_reporting.ini:/usr/local/etc/php/conf.d/error_reporting.ini" + + npm: + <<: *tool-service + container_name: leantime-npm + image: node:lts-alpine + entrypoint: ["npm"] + + npx: + <<: *tool-service + container_name: leantime-npx + image: node:lts-alpine + entrypoint: ["npx"] + + selenium: + <<: *tool-service + container_name: leantime-selenium + image: selenium/standalone-firefox:latest + ports: + - "4444:4444" + - "7900:7900" + + codeception: + <<: *tool-service + container_name: leantime-codeception + depends_on: + - selenium + image: codeception/codeception:latest diff --git a/.dev/docker-compose.yaml b/.dev/docker-compose.yaml index 3db55eb738..e38de957d4 100644 --- a/.dev/docker-compose.yaml +++ b/.dev/docker-compose.yaml @@ -1,23 +1,24 @@ -version: "3.9" networks: leantime: - external: false - driver: bridge + external: false + driver: bridge + volumes: mysql: s3ninja-data: services: leantime-dev: + container_name: leantime-dev privileged: true build: . ports: - - "8090:8080" + - "127.0.0.1:8090:8080" volumes: - - "../:/var/www/html" - - "./xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini" - - "./error_reporting.ini:/usr/local/etc/php/conf.d/error_reporting.ini" - - ".env:/var/www/html/config/.env" + - "${PWD}:/var/www/html" + - "${PWD}/.dev/xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini" + - "${PWD}/.dev/error_reporting.ini:/usr/local/etc/php/conf.d/error_reporting.ini" + - "${PWD}/.dev/.env:/var/www/html/config/.env" extra_hosts: - "host.docker.internal:host-gateway" depends_on: @@ -25,8 +26,10 @@ services: condition: service_healthy networks: - leantime + db: image: mysql:8.0 + container_name: db ports: - 3306:3306 environment: @@ -43,8 +46,10 @@ services: interval: 5s timeout: 5s retries: 20 + maildev: image: maildev/maildev + container_name: maildev environment: - MAILDEV_SMTP_PORT=465 - MAILDEV_WEB_PORT=8081 @@ -52,8 +57,10 @@ services: - 8081:8081 networks: - leantime + phpmyadmin: image: phpmyadmin + container_name: phpmyadmin ports: - 8082:80 environment: @@ -61,8 +68,10 @@ services: - PMA_PORT=3306 networks: - leantime + s3ninja: image: scireum/s3-ninja + container_name: s3ninja ports: - 8083:9000 networks: diff --git a/README.md b/README.md index 3fb03ccb01..6f2b01ef56 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ There are two ways to install a development setup of LeanTime. The first (but mo #### Development Installation via Docker #### -For development, we use a dockerized development environment. You will need to have ``docker``, ``docker compose``, ``make``, ``composer``, ``git`` and ``npm`` installed. +For development, we use a dockerized development environment. You will need to have ``docker``, ``docker compose``, and ``make`` installed. * Notes for Windows Environments: - Run all commands within the git bash terminal in order to utilize unix specific commands diff --git a/makefile b/makefile index 0d843b4e64..834fa719d2 100644 --- a/makefile +++ b/makefile @@ -1,23 +1,49 @@ +export COMPOSE_PROJECT_NAME=leantime-make + VERSION := $(shell grep "appVersion" ./app/Core/AppSettings.php |awk -F' = ' '{print substr($$2,2,length($$2)-3)}') -TARGET_DIR:= ./target/leantime -DOCS_DIR:= ./builddocs -DOCS_REPO:= git@github.com:Leantime/docs.git -RUNNING_DOCKER_CONTAINERS:= $(shell docker ps -a -q) -RUNNING_DOCKER_VOLUMES:= $(shell docker volume ls -q) +TARGET_DIR := ./target/leantime +DOCS_DIR := ./builddocs +DOCS_REPO := git@github.com:Leantime/docs.git +RUNNING_DOCKER_CONTAINERS := $(shell docker compose ps -a -q) +RUNNING_DOCKER_VOLUMES := $(shell docker volume ls -q) +UID := $(shell id -u) +GID := $(shell id -g) + +# Base compose file +COMPOSE_FILES := -f .dev/docker-compose.yaml + +# If local docker-compose file exists, add it to the command +ifneq ("$(wildcard .dev/docker-compose.local.yaml)","") + COMPOSE_FILES += " -f .dev/docker-compose.local.yaml" +endif + +# Compose files for tests +COMPOSE_FILES_TEST := $(COMPOSE_FILES) -f .dev/docker-compose.tests.yaml + +# Command aliases for running tools in docker +run-tool := docker compose -f .dev/docker-compose.tools.yaml run --rm --user "$(UID):$(GID)" +run-composer := $(run-tool) composer +run-npx := $(run-tool) npx +run-npm := $(run-tool) npm +run-php := $(run-tool) php +run-codeception := $(run-tool) codeception + +# Alias for running SQL against the test database +run-sql-test := docker compose $(COMPOSE_FILES_TEST) exec -T db mysql -hlocalhost -P3307 -uroot -pleantime -e install-deps-dev: - npm install --only=dev - composer install --optimize-autoloader + $(run-npm) install --only=dev + $(run-composer) install --optimize-autoloader install-deps: - npm install - composer install --no-dev --optimize-autoloader + $(run-npm) install + $(run-composer) install --no-dev --optimize-autoloader build: install-deps - npx mix --production + $(run-npx) mix --production build-dev: install-deps-dev - npx mix --production + $(run-npx) mix --production package: clean build mkdir -p $(TARGET_DIR) @@ -84,7 +110,7 @@ gendocs: # Requires github CLI (brew install gh) # Generate the docs phpDocumentor - php vendor/bin/leantime-documentor parse app --format=markdown --template=templates/markdown.php --output=builddocs/technical/hooks.md --memory-limit=-1 + $(run-php) vendor/bin/leantime-documentor parse app --format=markdown --template=templates/markdown.php --output=builddocs/technical/hooks.md --memory-limit=-1 # create pull request cd $(DOCS_DIR) && git switch -c "release/$(VERSION)" @@ -96,38 +122,52 @@ gendocs: # Requires github CLI (brew install gh) # Delete the temporary docs directory rm -rf $(DOCS_DIR) - clean: rm -rf $(TARGET_DIR) -run-dev: build-dev - cd .dev && docker-compose up --build --remove-orphans +docker-clean: + docker compose $(COMPOSE_FILES) down -v -acceptance-test: build-dev - php vendor/bin/codecept run Acceptance --steps +docker-clean-test: + docker compose $(COMPOSE_FILES_TEST) down -v -acceptance-test-ci: build-dev - php vendor/bin/codecept build -ifeq ($(strip $(RUNNING_DOCKER_CONTAINERS)),) - @echo "No running docker containers found" -else - docker rm -f $(RUNNING_DOCKER_CONTAINERS) -endif -ifeq ($(strip $(RUNNING_DOCKER_VOLUMES)),) - @echo "No running docker volumes found" -else - docker volume rm $(RUNNING_DOCKER_VOLUMES) -endif - php vendor/bin/codecept run Acceptance --steps +run-dev: build-dev docker-clean + docker compose $(COMPOSE_FILES) up -d --build --remove-orphans + make set-folder-permissions + +stop-dev: + make docker-clean + +run-test: build-dev docker-clean-test + docker compose $(COMPOSE_FILES_TEST) up -d --build --remove-orphans + make set-folder-permissions + +stop-test: + make docker-clean-test + +acceptance-test: run-test create-test-database + $(run-php) ./vendor/bin/codecept run Acceptance --steps + make stop-test + +acceptance-test-ci: run-test create-test-database + $(run-codeception) build + $(run-codeception) run Acceptance --steps + make stop-test codesniffer: - ./vendor/squizlabs/php_codesniffer/bin/phpcs app + $(run-php) ./vendor/squizlabs/php_codesniffer/bin/phpcs app codesniffer-fix: - ./vendor/squizlabs/php_codesniffer/bin/phpcbf app + $(run-php) ./vendor/squizlabs/php_codesniffer/bin/phpcbf app -get-version: - @echo $(VERSION) +create-test-database: run-test + $(run-sql-test) "DROP DATABASE IF EXISTS leantime_test;" + $(run-sql-test) "CREATE DATABASE IF NOT EXISTS leantime_test; GRANT ALL PRIVILEGES ON leantime_test.* TO 'leantime'@'%'; FLUSH PRIVILEGES;" -.PHONY: install-deps build package clean run-dev +set-folder-permissions: + docker compose exec -T leantime-dev chown -R www-data:www-data /var/www/html/cache/ + +get-version: + @echo $(VERSION) +.PHONY: install-deps build package clean run-dev run-test diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e697c8a0ed..bf27fb3bf2 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,9 +2,6 @@ declare(strict_types=1); -use Symfony\Component\Process\Exception\ProcessFailedException; -use Symfony\Component\Process\Process; - // Don't run script unless using the 'run' command if (! isset($_SERVER['argv'][1]) || $_SERVER['argv'][1] !== 'run') { return; @@ -26,16 +23,6 @@ */ protected static $instance; - /** - * @var Process - */ - protected Process $seleniumProcess; - - /** - * @var Process - */ - protected Process $dockerProcess; - /** * Get the singleton instance of this class * @@ -54,228 +41,97 @@ public static function getInstance(): self /** * Start the testing environment * + * @deprecated We do this in the makefile now * @access public * @return void */ public function start(): void { - $this->startDevEnvironment(); - $this->setFolderPermissions(); - $this->createDatabase(); - $this->startSelenium(); - $this->createStep('Starting Codeception Testing Framework'); + return; } /** * Destroy the testing environment * + * @deprecated We do this in the makefile now * @access public * @return void */ public function destroy(): void { - $this->createStep('Stopping Codeception Testing Framework'); - $this->stopSelenium(); - $this->stopDevEnvironment(); + return; } /** * Stop Selenium * + * @deprecated We do this in the makefile now * @access protected * @return void */ protected function stopSelenium(): void { - $this->createStep('Stopping Selenium'); - - try { - $this->seleniumProcess->stop(); - // we want the script to continue even if failure - } catch (Throwable $e) { - return; - } + return; } /** * Stop the dev environment * + * @deprecated We do this in the makefile now * @access protected * @return void */ protected function stopDevEnvironment(): void { - $this->createStep('Stopping Leantime Dev Environment'); - - foreach ( - [ - fn () => $this->dockerProcess->stop(), - fn () => $this->executeCommand('docker compose down', ['cwd' => DEV_ROOT]), - ] as $count => $shutdown - ) { - try { - $shutdown(); - // we want the script to continue even if failure - } catch (Throwable $e) { - if ($count === 1) { - return; - } - - continue; - } - } + return; } /** - * Start the dev environment + * Start the dev environment and set up DB connection * + * @deprecated We do this in the makefile now * @access protected * @return void */ protected function startDevEnvironment(): void { - $this->createStep('Build & Start Leantime Dev Environment'); - $hasLocalOverride = file_exists(DEV_ROOT . 'docker-compose.local.yaml'); - $this->dockerProcess = $this->executeCommand( - array_filter( - [ - 'docker', - 'compose', - '-f', - 'docker-compose.yaml', - '-f', - 'docker-compose.tests.yaml', - $hasLocalOverride ? '-f' : null, - $hasLocalOverride ? 'docker-compose.local.yaml' : null, - 'up', - '-d', - '--build', - '--remove-orphans', - ] - ), - [ - 'cwd' => DEV_ROOT, - 'background' => true, - 'timeout' => 0, - ] - ); - - $this->dockerProcess->waitUntil(function ($type, $buffer) { - if (! isset($started)) { - static $started = [ - 'dev-maildev-1' => false, - 'dev-db-1' => false, - 'dev-s3ninja-1' => false, - 'dev-phpmyadmin-1' => false, - 'dev-leantime-dev-1' => false, - ]; - } - - foreach ($started as $container => $status) { - if (! $status && strpos($buffer, "Container \"$container\" started") !== false) { - $started[$container] = true; - } - } - - $this->commandOutputHandler($type, $buffer); - - return ! in_array(false, $started, true); - }); + return; } /** * Create the test database * + * @deprecated We do this in the makefile now * @access protected * @return void */ protected function createDatabase(): void { - $this->createStep('Creating Test Database'); - $this->executeCommand( - [ - 'docker', - 'compose', - 'exec', - '-T', - 'db', - 'mysql', - '-hlocalhost', - '-uroot', - '-pleantime', - '-e', - 'DROP DATABASE IF EXISTS leantime_test;', - ], - ['cwd' => DEV_ROOT] - ); - $this->executeCommand( - [ - 'docker', - 'compose', - 'exec', - '-T', - 'db', - 'mysql', - '-hlocalhost', - '-uroot', - '-pleantime', - '-e', - 'CREATE DATABASE IF NOT EXISTS leantime_test; GRANT ALL PRIVILEGES ON leantime_test.* TO \'leantime\'@\'%\'; FLUSH PRIVILEGES;', - ], - ['cwd' => DEV_ROOT] - ); + return; } + /** + * Set folder permissions + * + * @deprecated We do this in the makefile now + * @access protected + * @return void + */ protected function setFolderPermissions(): void { - $this->createStep('Setting folder permissions on cache folder'); - - //Set file permissions - $this->executeCommand( - array_filter( - [ - 'docker', - 'compose', - 'exec', - '-T', - 'leantime-dev', - 'chown', - '-R', - 'www-data:www-data', - '/var/www/html/cache/', - ] - ), - [ - 'cwd' => DEV_ROOT, - ] - ); + return; } /** * Start Selenium * + * @deprecated We do this in the makefile now * @access protected * @return void */ protected function startSelenium(): void { - $this->createStep('Starting Selenium'); - $this->executeCommand( - [ - 'npx', - 'selenium-standalone', - 'install', - ] - ); - $this->seleniumProcess = $this->executeCommand([ - 'npx', - 'selenium-standalone', - 'start', - ], ['background' => true]); - $this->seleniumProcess->waitUntil(function ($type, $buffer) { - $this->commandOutputHandler($type, $buffer); - return strpos($buffer, 'Selenium started') !== false; - }); + return; } /** @@ -292,79 +148,6 @@ protected function createStep(string $message): void echo "\n$line\n$message\n$line\n"; } - - /** - * Execute a command - * - * @access protected - * @param string|array $command - * @param array $args - * @param boolean $required - * @return Process|string - */ - protected function executeCommand( - string|array $command, - array $args = [], - bool $required = true, - ): Process|string { - $process = is_array($command) - ? new Process($command) - : Process::fromShellCommandline($command); - - if (isset($args['cwd'])) { - $process->setWorkingDirectory($args['cwd']); - } - - if (isset($args['timeout'])) { - $process->setTimeout($args['timeout']); - } - - if (isset($args['options'])) { - $process->setOptions($args['options']); - } - - if (isset($args['background']) && $args['background']) { - $process->start(); - } else { - $process->run(fn ($type, $buffer) => $this->commandOutputHandler($type, $buffer)); - } - - if ( - $required - && (! isset($args['background']) || ! $args['background']) - && ! $process->isSuccessful() - ) { - throw new ProcessFailedException($process); - } - - if ( - isset($args['getOutput']) - && $args['getOutput'] - ) { - if (isset($args['background']) && $args['background']) { - throw new RuntimeException('Cannot get output from background process'); - } - - return $process->getOutput(); - } - - return $process; - } - - /** - * Handle command output - * - * @access private - * @param string $type - * @param string $buffer - * @return void - */ - private function commandOutputHandler(string $type, string $buffer): void - { - echo Process::ERR === $type - ? "\nSTDERR: $buffer" - : "\nSTDOUT: $buffer"; - } }); register_shutdown_function(fn () => $bootstrapper::getInstance()->destroy());