Bitbucket pipeline for Drupal browser test
Asked by Kristof De Bie:

I am trying to run Drupal browsertests/functional-tests in a Bitbucket pipeline, but always get the following error while doing so:

```
Exception: Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database: INSERT INTO "test35378103"."watchdog"...
```

The repository is just an empty/clean Drupal install as we were already facing the same issue on our actual repo. So we thought we would give it a try on a clean install so we could rule out any custom changes we have done.

#### Additional info: {#toc-hId-347664149}

* The sqlite.php file that is being called is a small script I have downloaded just to check that sqlite is actually working correctly. It opens a sqlite-db (in the same folder as where phpunit should do it), creates a table, inserts some data and selects that data again (and closes the db). All without any error.
* Apache seems to be running correctly, as the three curl-command all give the expected output.
* I have tried different docker-images, but I always run into the exact same exception "attempt to write a readonly database" when running the functional tests.
* I have tried placing the db in a lot of different locations (like /tmp and sorts), but I always have the same exception.
* I have tried switching to using MySql instead of Sqlite for phpunit (although the latter is prefered), but then the browsertest would also fail with an exception on some sort of file-permissions issue.
* I have also tried *"sqlite://localhost/:memory:"* as db-connection-string, but that also does not work. The database will be removed from memory after closing the connection to it. It looks like Drupal/phpunit is able to do it's setup for the functional test, but then when actually running the test-method a new db-connection is opened and thus all setup is gone again). Then when calling the */user* url, Drupal is redirecting to the install.php page (which is normal behaviour as there are no db-tables at that moment).
* Running the same functional-test in the same docker-image locally (+ running the same commands as from the pipeline before) is working just fine: not a single error.
* After a lot of searching on the internet almost all "solutions" for this exception mention it is a permission problem. So I have tried changing the permissions to the folder where the db should live and creating an empty db-file + given it all permissions as well: no luck...

Did anyone encounter a similar problem before? What is/could be the solution? I am out of ideas...

Happy to provide extra info if needed (and if I am able to share it).

*** ** * ** ***

**bitbucket-pipelines.yml**

```
image: php:8.2-apache
pipelines:
  default:
    - step:
        name: Run Drupal phpunit (unit and functional)
        caches:
          - composer
          - drush
        script:
          # Change the docroot of apache to match the bitbucket directory.
          - sed -i 's#DocumentRoot /var/www/html#DocumentRoot /opt/atlassian/pipelines/agent/build/web#g' /etc/apache2/sites-available/000-default.conf
          - cat /etc/apache2/sites-available/000-default.conf
          - sed -i 's#<Directory /var/www/>#<Directory /opt/atlassian/pipelines/agent/build/>#g' /etc/apache2/apache2.conf
          # Make sure .htaccess file are taken into account. We are changing this here for all directories listed in the .conf,
          # but that is not an issue for this usecase.
          - sed -i 's#AllowOverride None#AllowOverride All#g' /etc/apache2/apache2.conf
          # Install gd
          - apt-get update && apt-get install -y libfreetype-dev libjpeg62-turbo-dev libpng-dev && docker-php-ext-configure gd --with-freetype --with-jpeg && docker-php-ext-install -j$(nproc) gd
          # Update the container and install zip so composer can
          # use zip to unpack the dependencies.
          - apt update -y
          - apt install -y zip unzip

          # Install dependencies
          - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
          - export COMPOSER_ALLOW_SUPERUSER=1
          - composer install -o

          # Create the folder for the browser-output
          - mkdir $(pwd)/web/sites/simpletest
          - mkdir $(pwd)/web/sites/simpletest/browser_output

          # For testing only: create an empty db-file
          - touch $(pwd)/web/sites/simpletest/phpunit.sqlite

          # For testing only: set all permissions open
          - chown -R www-data:www-data $(pwd)/web/sites/simpletest
          - chmod -R 777 $(pwd)/web/sites/simpletest

          # Start Apache
          - apachectl restart
          # Wait for a few seconds to ensure the server starts
          - sleep 5

          # Check if the server is running and serving the expected content
          # This should give a redirect to install.php
          - curl -vk http://localhost
          # This should also give a redirect to install.php
          - curl -vk http://localhost/user
          # This should give some output using a sqlite-db
          - curl -vk http://localhost/sqlite.php

          # Run tests
          # Create a link from phpunit.xml to phpunit.xml.dist (as the first one
          # is not a part of the repo).
          - ln -s phpunit.xml.dist phpunit.xml

          # For testing only: set all permissions open
          - chown -R www-data:www-data /opt/atlassian/pipelines/agent/build
          - chmod -R 777 /opt/atlassian/pipelines/agent/build

          # Run tests
          - ./vendor/bin/phpunit --debug -v -c $(pwd)/phpunit.xml web/modules/custom/sandbox/Tests/src/Unit
          - ./vendor/bin/phpunit --debug -v -c $(pwd)/phpunit.xml web/modules/custom/sandbox/Tests/src/Functional

definitions:
  caches:
    composer: /root/.composer/cache
    drush: ~/.drush/cache
```

**partial content of phpunit.xml(.dist)**

```
<env name="SIMPLETEST_BASE_URL" value="http://localhost"/>
<env name="SIMPLETEST_DB" value="sqlite://localhost//opt/atlassian/pipelines/agent/build/web/sites/simpletest/phpunit.sqlite"/>
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value="/opt/atlassian/pipelines/agent/build/web/sites/simpletest/browser_output"/>
```


Answers:
author: Kristof De Bie
body: **output of the exception in the pipeline**

```
./vendor/bin/phpunit --debug -v -c $(pwd)/phpunit.xml web/modules/custom/sandbox/Tests/src/Functional
PHPUnit 9.6.19 by Sebastian Bergmann and contributors.
Runtime:?????? PHP 8.2.20
Configuration: /opt/atlassian/pipelines/agent/build/phpunit.xml
Testing /opt/atlassian/pipelines/agent/build/web/modules/custom/sandbox/Tests/src/Functional
Test 'Drupal\Tests\sandbox\Functional\SampleTest::testSample' started
Test 'Drupal\Tests\sandbox\Functional\SampleTest::testSample' ended
Time: 00:02.810, Memory: 10.00 MB
There was 1 error:
1) Drupal\Tests\sandbox\Functional\SampleTest::testSample
Exception: Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database: INSERT INTO "test69483024"."watchdog" ("uid", "type", "message", "variables", "severity", "link", "location", "referer", "hostname", "timestamp") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); Array
(
??? [0] => 0
??? [1] => php
??? [2] => %type: @message in %function (line %line of %file).
??? [3] => a:6:{s:5:"%type";s:45:"Drupal\Core\Database\DatabaseExceptionWrapper";s:8:"@message";s:4384:"SQLSTATE[HY000]: General error: 8 attempt to write a readonly database: INSERT INTO "test69483024"."cache_data" ("cid", "expire", "created", "tags", "checksum", "data", "serialized") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6) ON CONFLICT ("cid") DO UPDATE SET "cid" = EXCLUDED."cid", "expire" = EXCLUDED."expire", "created" = EXCLUDED."created", "tags" = EXCLUDED."tags", "checksum" = EXCLUDED."checksum", "data" = EXCLUDED."data", "serialized" = EXCLUDED."serialized"; Array
(
??? [:db_insert_placeholder_0] => route:[language]=en:/:
??? [:db_insert_placeholder_1] => -1
??? [:db_insert_placeholder_2] => 1720006065.735
??? [:db_insert_placeholder_3] => route_match
??? [:db_insert_placeholder_4] => 1
??? [:db_insert_placeholder_5] => a:3:{s:4:"path";s:11:"/user/login";s:5:"query";a:0:{}s:6:"routes";O:41:"Symfony\Component\Routing\RouteCollection":4:{s:49:"Symfony\Component\Routing\RouteCollectionroutes";a:3:{s:10:"user.login";O:31:"Symfony\Component\Routing\Route":9:{s:4:"path";s:11:"/user/login";s:4:"host";s:0:"";s:8:"defaults";a:2:{s:5:"_form";s:31:"\Drupal\user\Form\UserLoginForm";s:6:"_title";s:6:"Log in";}s:12:"requirements";a:1:{s:18:"_user_is_logged_in";s:5:"FALSE";}s:7:"options";a:4:{s:14:"compiler_class";s:33:"Drupal\Core\Routing\RouteCompiler";s:19:"_maintenance_access";b:1;s:4:"utf8";b:1;s:14:"_access_checks";a:1:{i:0;s:30:"access_check.user.login_status";}}s:7:"schemes";a:0:{}s:7:"methods";a:2:{i:0;s:3:"GET";i:1;s:4:"POST";}s:9:"condition";s:0:"";s:8:"compiled";O:33:"Drupal\Core\Routing\CompiledRoute":11:{s:4:"vars";a:0:{}s:11:"path_prefix";s:0:"";s:10:"path_regex";s:18:"{^/user/login$}sDu";s:11:"path_tokens";a:1:{i:0;a:2:{i:0;s:4:"text";i:1;s:11:"/user/login";}}s:9:"path_vars";a:0:{}s:10:"host_regex";N;s:11:"host_tokens";a:0:{}s:9:"host_vars";a:0:{}s:3:"fit";i:3;s:14:"patternOutline";s:11:"/user/login";s:8:"numParts";i:2;}}s:15:"user.login.http";O:31:"Symfony\Component\Routing\Route":9:{s:4:"path";s:11:"/user/login";s:4:"host";s:0:"";s:8:"defaults";a:1:{s:11:"_controller";s:59:"\Drupal\user\Controller\UserAuthenticationController::login";}s:12:"requirements";a:2:{s:18:"_user_is_logged_in";s:5:"FALSE";s:7:"_format";s:4:"json";}s:7:"options";a:3:{s:14:"compiler_class";s:33:"Drupal\Core\Routing\RouteCompiler";s:4:"utf8";b:1;s:14:"_access_checks";a:1:{i:0;s:30:"access_check.user.login_status";}}s:7:"schemes";a:0:{}s:7:"methods";a:1:{i:0;s:4:"POST";}s:9:"condition";s:0:"";s:8:"compiled";O:33:"Drupal\Core\Routing\CompiledRoute":11:{s:4:"vars";a:0:{}s:11:"path_prefix";s:0:"";s:10:"path_regex";s:18:"{^/user/login$}sDu";s:11:"path_tokens";a:1:{i:0;a:2:{i:0;s:4:"text";i:1;s:11:"/user/login";}}s:9:"path_vars";a:0:{}s:10:"host_regex";N;s:11:"host_tokens";a:0:{}s:9:"host_vars";a:0:{}s:3:"fit";i:3;s:14:"patternOutline";s:11:"/user/login";s:8:"numParts";i:2;}}s:21:"entity.user.canonical";O:31:"Symfony\Component\Routing\Route":9:{s:4:"path";s:12:"/user/{user}";s:4:"host";s:0:"";s:8:"defaults";a:2:{s:12:"_entity_view";s:9:"user.full";s:15:"_title_callback";s:48:"Drupal\user\Controller\UserController::userTitle";}s:12:"requirements";a:2:{s:4:"user";s:3:"\d+";s:14:"_entity_access";s:9:"user.view";}s:7:"options";a:4:{s:14:"compiler_class";s:33:"Drupal\Core\Routing\RouteCompiler";s:10:"parameters";a:1:{s:4:"user";a:2:{s:4:"type";s:11:"entity:user";s:9:"converter";s:21:"paramconverter.entity";}}s:14:"_access_checks";a:1:{i:0;s:19:"access_check.entity";}s:4:"utf8";b:1;}s:7:"schemes";a:0:{}s:7:"methods";a:2:{i:0;s:3:"GET";i:1;s:4:"POST";}s:9:"condition";s:0:"";s:8:"compiled";O:33:"Drupal\Core\Routing\CompiledRoute":11:{s:4:"vars";a:1:{i:0;s:4:"user";}s:11:"path_prefix";s:0:"";s:10:"path_regex";s:26:"{^/user/(?P<user>\d+)$}sDu";s:11:"path_tokens";a:2:{i:0;a:5:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:4:"user";i:4;b:1;}i:1;a:2:{i:0;s:4:"text";i:1;s:5:"/user";}}s:9:"path_vars";a:1:{i:0;s:4:"user";}s:10:"host_regex";N;s:11:"host_tokens";a:0:{}s:9:"host_vars";a:0:{}s:3:"fit";i:2;s:14:"patternOutline";s:7:"/user/%";s:8:"numParts";i:2;}}}s:50:"Symfony\Component\Routing\RouteCollectionaliases";a:0:{}s:52:"Symfony\Component\Routing\RouteCollectionresources";a:0:{}s:53:"Symfony\Component\Routing\RouteCollectionpriorities";a:0:{}}}
??? [:db_insert_placeholder_6] => 1
)...
```


author: Kristof De Bie
body: edited: double post


author: Kristof De Bie
body: (cannot put all info in post: adding here in replies instead)

**SampleTest**

```
<?php

namespace Drupal\Tests\sandbox\Functional;

use Drupal\Tests\BrowserTestBase;

class SampleTest extends BrowserTestBase {
? protected static $modules = [
??? 'system',
??? 'node',
??? 'dblog',
? ];

? protected $defaultTheme = 'olivero';

? protected function setUp(): void {
??? parent::setUp();
? }

? public function testSample() {
??? $this->drupalGet('user');
??? $this->assertSession()->pageTextContains('Log in');
? }
}
```

comments: author: Kristof De Bie
body: edited: double post


Tags: bitbucket-cloud

bitbucket-pipelines

cloud

drupal

php

phpunit