diff --git a/builder/gen-dockerfile/src/Builder/GenFilesCommand.php b/builder/gen-dockerfile/src/Builder/GenFilesCommand.php
index d3409186..cc9fddf1 100644
--- a/builder/gen-dockerfile/src/Builder/GenFilesCommand.php
+++ b/builder/gen-dockerfile/src/Builder/GenFilesCommand.php
@@ -57,6 +57,12 @@ protected function configure()
$this
->setName('create')
->setDescription('Create Dockerfile and .dockerignore file')
+ ->addOption(
+ 'php74-image',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'The PHP 74 base image of the Dockerfile'
+ )
->addOption(
'php73-image',
null,
@@ -111,17 +117,17 @@ protected function initialize(InputInterface $input, OutputInterface $output)
$output->writeln("
There is no PHP runtime version specified in composer.json, or
we don't support the version you specified. Google App Engine
-uses the latest 7.3.x version.
+uses the latest 7.4.x version.
We recommend pinning your PHP version by running:
-composer require php 7.3.* (replace it with your desired minor version)
+composer require php 7.4.* (replace it with your desired minor version)
-Using PHP version 7.3.x...
+Using PHP version 7.4.x...
");
} elseif ($version === DetectPhpVersion::EXACT_VERSION_SPECIFIED) {
throw new ExactVersionException(
"An exact PHP version was specified in composer.json. Please pin your" .
- "PHP version to a minor version such as '7.3.*'."
+ "PHP version to a minor version such as '7.4.*'."
);
}
if (substr($version, 0, 3) === '5.6') {
@@ -132,8 +138,10 @@ protected function initialize(InputInterface $input, OutputInterface $output)
$this->detectedPhpVersion = '7.1';
} elseif (substr($version, 0, 3) === '7.2') {
$this->detectedPhpVersion = '7.2';
- } else {
+ } elseif (substr($version, 0, 3) === '7.3') {
$this->detectedPhpVersion = '7.3';
+ } else {
+ $this->detectedPhpVersion = '7.4';
}
$yamlPath = getenv('GAE_APPLICATION_YAML_PATH')
?: self::DEFAULT_YAML_PATH;
diff --git a/builder/gen-dockerfile/src/DetectPhpVersion.php b/builder/gen-dockerfile/src/DetectPhpVersion.php
index bf4abae1..dc782906 100644
--- a/builder/gen-dockerfile/src/DetectPhpVersion.php
+++ b/builder/gen-dockerfile/src/DetectPhpVersion.php
@@ -98,6 +98,7 @@ public static function isExactVersion($constraint)
private static function detectAvailableVersions()
{
return [
+ trim(file_get_contents('/opt/php74_version')),
trim(file_get_contents('/opt/php73_version')),
trim(file_get_contents('/opt/php72_version')),
trim(file_get_contents('/opt/php71_version')),
diff --git a/builder/gen-dockerfile/tests/GenFilesCommandTest.php b/builder/gen-dockerfile/tests/GenFilesCommandTest.php
index b3df20a7..576379fb 100644
--- a/builder/gen-dockerfile/tests/GenFilesCommandTest.php
+++ b/builder/gen-dockerfile/tests/GenFilesCommandTest.php
@@ -79,6 +79,7 @@ public function testGenFilesCommand(
'--php71-image' => 'gcr.io/google-appengine/php71:latest',
'--php72-image' => 'gcr.io/google-appengine/php72:latest',
'--php73-image' => 'gcr.io/google-appengine/php73:latest',
+ '--php74-image' => 'gcr.io/google-appengine/php74:latest',
];
}
if ($expectedException !== null) {
@@ -131,10 +132,10 @@ public function dataProvider()
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
["COMPOSER_FLAGS='--no-dev --prefer-dist' \\\n",
"FRONT_CONTROLLER_FILE='index.php' \\\n",
- "DETECTED_PHP_VERSION='7.3' \n"
+ "DETECTED_PHP_VERSION='7.4' \n"
]
],
[
@@ -144,11 +145,11 @@ public function dataProvider()
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
["COMPOSER_FLAGS='--no-dev --prefer-dist' \\\n",
"FRONT_CONTROLLER_FILE='index.php' \\\n",
"SKIP_LOCKDOWN_DOCUMENT_ROOT='true' \\\n",
- "DETECTED_PHP_VERSION='7.3' \n"
+ "DETECTED_PHP_VERSION='7.4' \n"
]
],
[
@@ -169,10 +170,10 @@ public function dataProvider()
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
["COMPOSER_FLAGS='--prefer-dist --no-dev --no-script' \\\n",
"FRONT_CONTROLLER_FILE='index.php' \\\n",
- "DETECTED_PHP_VERSION='7.3' \n"
+ "DETECTED_PHP_VERSION='7.4' \n"
]
],
[
@@ -212,6 +213,19 @@ public function dataProvider()
"DETECTED_PHP_VERSION='7.3' \n"
]
],
+ [
+ // PHP 7.4
+ __DIR__ . '/test_data/php74',
+ null,
+ '',
+ '/app',
+ 'added by the php runtime builder',
+ 'gcr.io/google-appengine/php74:latest',
+ ["COMPOSER_FLAGS='--no-dev --prefer-dist' \\\n",
+ "FRONT_CONTROLLER_FILE='index.php' \\\n",
+ "DETECTED_PHP_VERSION='7.4' \n"
+ ]
+ ],
[
// values on env_variables
__DIR__ . '/test_data/values_only_on_env',
@@ -219,7 +233,7 @@ public function dataProvider()
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
[
"WHITELIST_FUNCTIONS='exec' \\\n",
"FRONT_CONTROLLER_FILE='app.php'",
@@ -239,7 +253,7 @@ public function dataProvider()
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
[],
'\\Google\\Cloud\\Runtimes\\Builder\\Exception\\MissingDocumentRootException'
],
@@ -250,7 +264,7 @@ public function dataProvider()
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
[
"WHITELIST_FUNCTIONS='exec' \\\n",
"FRONT_CONTROLLER_FILE='app.php'",
@@ -271,7 +285,7 @@ public function dataProvider()
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
["FRONT_CONTROLLER_FILE='app.php' \\\n"]
],
[
@@ -281,7 +295,7 @@ public function dataProvider()
'my.yaml',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest'
+ 'gcr.io/google-appengine/php74:latest'
],
[
// Overrides baseImage
@@ -292,11 +306,12 @@ public function dataProvider()
'--php71-image' => 'gcr.io/php-mvm-a-28051/php71:latest',
'--php72-image' => 'gcr.io/php-mvm-a-28051/php72:latest',
'--php73-image' => 'gcr.io/php-mvm-a-28051/php73:latest',
+ '--php74-image' => 'gcr.io/php-mvm-a-28051/php74:latest',
],
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/php-mvm-a-28051/php73:latest'
+ 'gcr.io/php-mvm-a-28051/php74:latest'
],
[
// Has document_root set
@@ -305,7 +320,7 @@ public function dataProvider()
'',
'/app/web',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest'
+ 'gcr.io/google-appengine/php74:latest'
],
[
// Has document_root set in env_variables
@@ -314,7 +329,7 @@ public function dataProvider()
'',
'/app/web',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest'
+ 'gcr.io/google-appengine/php74:latest'
],
[
// document_root in both will throw exception
@@ -323,7 +338,7 @@ public function dataProvider()
'',
'/app/web',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
[],
'\\Google\\Cloud\\Runtimes\\Builder\\Exception\\EnvConflictException'
],
@@ -343,7 +358,7 @@ public function dataProvider()
'',
'/app',
'added by the php runtime builder',
- 'gcr.io/google-appengine/php73:latest',
+ 'gcr.io/google-appengine/php74:latest',
[],
'\\Google\\Cloud\\Runtimes\\Builder\\Exception\\ExactVersionException'
]
diff --git a/builder/gen-dockerfile/tests/test_data/php74/app.yaml b/builder/gen-dockerfile/tests/test_data/php74/app.yaml
new file mode 100644
index 00000000..2359f78c
--- /dev/null
+++ b/builder/gen-dockerfile/tests/test_data/php74/app.yaml
@@ -0,0 +1,5 @@
+env: flex
+runtime: php
+
+runtime_config:
+ document_root: /app
diff --git a/builder/gen-dockerfile/tests/test_data/php74/composer.json b/builder/gen-dockerfile/tests/test_data/php74/composer.json
new file mode 100644
index 00000000..3a5b11f1
--- /dev/null
+++ b/builder/gen-dockerfile/tests/test_data/php74/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "php": "7.4.*"
+ }
+}
diff --git a/builder/php-latest.yaml b/builder/php-latest.yaml
index 4f946b36..e015ece7 100644
--- a/builder/php-latest.yaml
+++ b/builder/php-latest.yaml
@@ -1,6 +1,6 @@
steps:
- name: 'gcr.io/gcp-runtimes/php/gen-dockerfile:latest'
- args: ['--php73-image', 'gcr.io/google-appengine/php73:latest', '--php72-image', 'gcr.io/google-appengine/php72:latest', '--php71-image', 'gcr.io/google-appengine/php71:latest', '--php70-image', 'gcr.io/google-appengine/php70:latest', '--php56-image', 'gcr.io/google-appengine/php56:latest']
+ args: ['--php74-image', 'gcr.io/google-appengine/php74:latest', '--php73-image', 'gcr.io/google-appengine/php73:latest', '--php72-image', 'gcr.io/google-appengine/php72:latest', '--php71-image', 'gcr.io/google-appengine/php71:latest', '--php70-image', 'gcr.io/google-appengine/php70:latest', '--php56-image', 'gcr.io/google-appengine/php56:latest']
env: 'GAE_APPLICATION_YAML_PATH=$_GAE_APPLICATION_YAML_PATH'
- name: 'gcr.io/kaniko-project/executor:v0.6.0'
args: ['--destination=$_OUTPUT_IMAGE']
diff --git a/check-versions/tests/VersionTest.php b/check-versions/tests/VersionTest.php
index 2021c30a..7d6bd116 100644
--- a/check-versions/tests/VersionTest.php
+++ b/check-versions/tests/VersionTest.php
@@ -45,6 +45,14 @@ public static function setUpBeforeClass()
'Failed to detect the latest PHP73 version';
}
+ $pattern = '/PHP (7\.4\.\d+)/';
+ if (preg_match($pattern, $body, $matches)) {
+ self::$versions['php74'] = $matches[1];
+ } else {
+ self::$versions['php74'] =
+ 'Failed to detect the latest PHP74 version';
+ }
+
exec('apt-get update');
}
@@ -69,4 +77,15 @@ public function testPHP73Version()
$this->fail('Failed to detect the current php73 version');
}
}
+
+ public function testPHP74Version()
+ {
+ $output = exec('apt-cache madison gcp-php74');
+ $pattern = '/(7\.4\.\d+)/';
+ if (preg_match($pattern, $output, $matches)) {
+ $this->assertEquals($matches[1], self::$versions['php74']);
+ } else {
+ $this->fail('Failed to detect the current php74 version');
+ }
+ }
}
diff --git a/cloudbuild-ubuntu.yaml b/cloudbuild-ubuntu.yaml
index e3bb3003..a3746ebb 100644
--- a/cloudbuild-ubuntu.yaml
+++ b/cloudbuild-ubuntu.yaml
@@ -22,6 +22,17 @@ steps:
waitFor: ['php-base']
id: php-base-structure
+ # php74
+ - name: gcr.io/cloud-builders/docker
+ args: ['build', '-t', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74:$_TAG', '--build-arg', 'PHP_VERSION=7.4', '.']
+ dir: php-versioned
+ waitFor: ['php-base']
+ id: php74
+ - name: gcr.io/gcp-runtimes/structure_test
+ args: ['-i', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74:$_TAG', '--config', '/workspace/php-versioned/php74.yaml', '-v']
+ waitFor: ['php74']
+ id: php74-structure
+
# php73
- name: gcr.io/cloud-builders/docker
args: ['build', '-t', 'gcr.io/${_GOOGLE_PROJECT_ID}/php73:$_TAG', '--build-arg', 'PHP_VERSION=7.3', '.']
@@ -126,6 +137,24 @@ steps:
waitFor: ['php71-custom-app', 'test-runner']
id: php71-custom-test
+ # php74-custom test
+ - name: gcr.io/cloud-builders/docker
+ args: ['build', '-t', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74-custom:$_TAG', '.']
+ dir: testapps/php74_custom
+ waitFor: ['php-onbuild']
+ id: php74-custom-build
+ - name: gcr.io/gcp-runtimes/structure_test
+ args: ['-i', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74-custom:$_TAG', '--config', 'php74.yaml', '-v']
+ waitFor: ['php74-custom-build']
+ - name: gcr.io/cloud-builders/docker
+ args: ['run', '--net=nw_$_TAG', '--name=php74-custom', '-d', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74-custom:$_TAG']
+ waitFor: ['php74-custom-build', 'test-network']
+ id: php74-custom-app
+ - name: gcr.io/cloud-builders/docker
+ args: ['run', '--net=nw_$_TAG','-v', '/workspace:/workspace', 'gcr.io/${_GOOGLE_PROJECT_ID}/php-test-runner:$_TAG', '/workspace/testapps/php74_custom/tests']
+ waitFor: ['php74-custom-app', 'test-runner']
+ id: php74-custom-test
+
# php73-custom test
- name: gcr.io/cloud-builders/docker
args: ['build', '-t', 'gcr.io/${_GOOGLE_PROJECT_ID}/php73-custom:$_TAG', '.']
@@ -222,9 +251,35 @@ steps:
waitFor: ['php73-extensions-build']
id: php73-extensions-legacy-test
+ # php74-extensions test
+ - name: gcr.io/cloud-builders/docker
+ args: [ 'build', '-t', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74-extensions:$_TAG', '.' ]
+ dir: testapps/php74_extensions
+ waitFor: [ 'php-onbuild' ]
+ id: php74-extensions-build
+ - name: gcr.io/cloud-builders/docker
+ args: [ 'run', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74-extensions:$_TAG', 'vendor/bin/phpunit' ]
+ dir: testapps/php74_extensions
+ waitFor: [ 'php74-extensions-build' ]
+ id: php74-extensions-test
+
+ # php74-extensions-legacy test
+ - name: gcr.io/cloud-builders/docker
+ args: [ 'build', '-t', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74-extensions:$_TAG', '.' ]
+ dir: testapps/php74_extensions_legacy
+ waitFor: [ 'php-onbuild' ]
+ id: php74-extensions-legacy-build
+ - name: gcr.io/cloud-builders/docker
+ args: [ 'run', 'gcr.io/${_GOOGLE_PROJECT_ID}/php74-extensions:$_TAG', 'vendor/bin/phpunit' ]
+ dir: testapps/php74_extensions_legacy
+ waitFor: [ 'php74-extensions-build' ]
+ id: php74-extensions-legacy-test
+
+
images:
- gcr.io/${_GOOGLE_PROJECT_ID}/php-base:$_TAG
- gcr.io/${_GOOGLE_PROJECT_ID}/php:$_TAG
+ - gcr.io/${_GOOGLE_PROJECT_ID}/php74:$_TAG
- gcr.io/${_GOOGLE_PROJECT_ID}/php73:$_TAG
- gcr.io/${_GOOGLE_PROJECT_ID}/php72:$_TAG
- gcr.io/${_GOOGLE_PROJECT_ID}/php71:$_TAG
diff --git a/integration-tests.yaml b/integration-tests.yaml
index 974f422c..f5db3c08 100644
--- a/integration-tests.yaml
+++ b/integration-tests.yaml
@@ -6,6 +6,17 @@ steps:
waitFor: ['-']
id: test-runner
+ # php74_e2e test
+ - name: gcr.io/${_GOOGLE_PROJECT_ID}/php-test-runner:$_TAG
+ args: ['/workspace/testapps/php74_e2e/tests']
+ waitFor: ['test-runner']
+ id: php74_e2e
+ env:
+ - 'SERVICE_ACCOUNT_JSON=${_SERVICE_ACCOUNT_JSON}'
+ - 'TAG=${_TAG}-e2e'
+ - 'E2E_PROJECT_ID=${_E2E_PROJECT_ID}'
+ - 'TEST_VM_IMAGE=${_TEST_VM_IMAGE}'
+
# php73_e2e test
- name: gcr.io/${_GOOGLE_PROJECT_ID}/php-test-runner:$_TAG
args: ['/workspace/testapps/php73_e2e/tests']
diff --git a/package-builder/debian/patches/php74-parse_str_harden.patch b/package-builder/debian/patches/php74-parse_str_harden.patch
new file mode 100644
index 00000000..e69de29b
diff --git a/package-builder/gpgkeys/php74/derick.key b/package-builder/gpgkeys/php74/derick.key
new file mode 100644
index 00000000..574fdf58
--- /dev/null
+++ b/package-builder/gpgkeys/php74/derick.key
@@ -0,0 +1,92 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+uNV2Pe9hGTB2+hDdxkmllksfJP2cNQ==
+=0twV
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/package-builder/gpgkeys/php74/petk.key b/package-builder/gpgkeys/php74/petk.key
new file mode 100644
index 00000000..f5a4b89d
--- /dev/null
+++ b/package-builder/gpgkeys/php74/petk.key
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+8nw=
+=DBgL
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/php-base/Dockerfile b/php-base/Dockerfile
index 14f4313a..b13f205c 100644
--- a/php-base/Dockerfile
+++ b/php-base/Dockerfile
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Dockerfile for PHP 7.1/7.2/7.3 using nginx as the webserver.
+# Dockerfile for PHP 7.1/7.2/7.3/7.4 using nginx as the webserver.
FROM gcr.io/gcp-runtimes/ubuntu_16_0_4
@@ -68,6 +68,7 @@ ENV NGINX_DIR=/etc/nginx \
PHP71_DIR=/opt/php71 \
PHP72_DIR=/opt/php72 \
PHP73_DIR=/opt/php73 \
+ PHP74_DIR=/opt/php74 \
APP_DIR=/app \
NGINX_USER_CONF_DIR=/etc/nginx/conf.d \
UPLOAD_DIR=/upload \
diff --git a/php-base/build-scripts/composer.sh b/php-base/build-scripts/composer.sh
index edced9f3..7775e14b 100644
--- a/php-base/build-scripts/composer.sh
+++ b/php-base/build-scripts/composer.sh
@@ -23,7 +23,7 @@ else
set -e
fi
-DEFAULT_PHP_VERSION="7.3"
+DEFAULT_PHP_VERSION="7.4"
if [ -f ${APP_DIR}/composer.json ]; then
if [ -n "${DETECTED_PHP_VERSION}" ]; then
@@ -36,16 +36,16 @@ if [ -f ${APP_DIR}/composer.json ]; then
if [ "${PHP_VERSION}" == "exact" ]; then
cat< cloudbuild-test-runner/Dockerfile
diff --git a/scripts/release.sh b/scripts/release.sh
index c488d9e0..2d7e68b6 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -19,6 +19,7 @@ scripts/build_images.sh
IMAGE_NAME="gcr.io/${GOOGLE_PROJECT_ID}/php:${TAG}"
BASE_IMAGE_NAME="gcr.io/${GOOGLE_PROJECT_ID}/php-base:${TAG}"
+PHP74_IMAGE_NAME="gcr.io/${GOOGLE_PROJECT_ID}/php74:${TAG}"
PHP73_IMAGE_NAME="gcr.io/${GOOGLE_PROJECT_ID}/php73:${TAG}"
PHP72_IMAGE_NAME="gcr.io/${GOOGLE_PROJECT_ID}/php72:${TAG}"
PHP71_IMAGE_NAME="gcr.io/${GOOGLE_PROJECT_ID}/php71:${TAG}"
@@ -28,6 +29,7 @@ if [ "${ADD_CANDIDATE_TAG}" = "true" ]; then
echo "CANDIDATE_TAG:${CANDIDATE_TAG}"
gcloud -q beta container images add-tag "${IMAGE_NAME}" "${DOCKER_NAMESPACE}/php:${CANDIDATE_TAG}"
gcloud -q beta container images add-tag "${BASE_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php-base:${CANDIDATE_TAG}"
+ gcloud -q beta container images add-tag "${PHP74_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php74:${CANDIDATE_TAG}"
gcloud -q beta container images add-tag "${PHP73_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php73:${CANDIDATE_TAG}"
gcloud -q beta container images add-tag "${PHP72_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php72:${CANDIDATE_TAG}"
gcloud -q beta container images add-tag "${PHP71_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php71:${CANDIDATE_TAG}"
@@ -37,6 +39,7 @@ fi
if [ "${ADD_STAGING_TAG}" = "true" ]; then
gcloud -q beta container images add-tag "${IMAGE_NAME}" "${DOCKER_NAMESPACE}/php:staging"
gcloud -q beta container images add-tag "${BASE_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php-base:staging"
+ gcloud -q beta container images add-tag "${PHP74_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php74:staging"
gcloud -q beta container images add-tag "${PHP73_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php73:staging"
gcloud -q beta container images add-tag "${PHP72_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php72:staging"
gcloud -q beta container images add-tag "${PHP71_IMAGE_NAME}" "${DOCKER_NAMESPACE}/php71:staging"
@@ -47,6 +50,7 @@ METADATA=$(pwd)/METADATA
cd ${KOKORO_GFILE_DIR}/kokoro
python note.py php -m ${METADATA} -t ${TAG}
python note.py php-base -m ${METADATA} -t ${TAG}
+python note.py php74 -m ${METADATA} -t ${TAG}
python note.py php73 -m ${METADATA} -t ${TAG}
python note.py php72 -m ${METADATA} -t ${TAG}
python note.py php71 -m ${METADATA} -t ${TAG}
diff --git a/scripts/ubuntu-packages.cfg b/scripts/ubuntu-packages.cfg
index d910bab7..5fae0316 100644
--- a/scripts/ubuntu-packages.cfg
+++ b/scripts/ubuntu-packages.cfg
@@ -5,6 +5,6 @@ env_vars {
env_vars {
key: "PHP_VERSIONS"
- value: "7.3.23-1,7.2.34-1,7.1.33-1"
+ value: "7.4.12-1,7.3.23-1,7.2.34-1,7.1.33-1"
}
diff --git a/testapps/integration-individual-packages/test.yaml.in b/testapps/integration-individual-packages/test.yaml.in
index 3836d968..5c63be92 100644
--- a/testapps/integration-individual-packages/test.yaml.in
+++ b/testapps/integration-individual-packages/test.yaml.in
@@ -1,6 +1,6 @@
steps:
- name: '${STAGING_BUILDER_IMAGE}'
- args: ['--php73-image', 'gcr.io/google-appengine/php73:staging', '--php72-image', 'gcr.io/google-appengine/php72:staging', '--php71-image', 'gcr.io/google-appengine/php71:staging']
+ args: ['--php74-image', 'gcr.io/google-appengine/php74:staging', '--php73-image', 'gcr.io/google-appengine/php73:staging', '--php72-image', 'gcr.io/google-appengine/php72:staging', '--php71-image', 'gcr.io/google-appengine/php71:staging']
- name: 'gcr.io/cloud-builders/docker:latest'
args: ['build', '-t', '$_OUTPUT_IMAGE', '.']
images:
diff --git a/testapps/integration/test.yaml.in b/testapps/integration/test.yaml.in
index 3836d968..5c63be92 100644
--- a/testapps/integration/test.yaml.in
+++ b/testapps/integration/test.yaml.in
@@ -1,6 +1,6 @@
steps:
- name: '${STAGING_BUILDER_IMAGE}'
- args: ['--php73-image', 'gcr.io/google-appengine/php73:staging', '--php72-image', 'gcr.io/google-appengine/php72:staging', '--php71-image', 'gcr.io/google-appengine/php71:staging']
+ args: ['--php74-image', 'gcr.io/google-appengine/php74:staging', '--php73-image', 'gcr.io/google-appengine/php73:staging', '--php72-image', 'gcr.io/google-appengine/php72:staging', '--php71-image', 'gcr.io/google-appengine/php71:staging']
- name: 'gcr.io/cloud-builders/docker:latest'
args: ['build', '-t', '$_OUTPUT_IMAGE', '.']
images:
diff --git a/testapps/php74_custom/.gitignore b/testapps/php74_custom/.gitignore
new file mode 100644
index 00000000..09a29da8
--- /dev/null
+++ b/testapps/php74_custom/.gitignore
@@ -0,0 +1,2 @@
+cloudbuild.yaml
+Dockerfile
diff --git a/testapps/php74_custom/Dockerfile.in b/testapps/php74_custom/Dockerfile.in
new file mode 100644
index 00000000..2b1ce1c8
--- /dev/null
+++ b/testapps/php74_custom/Dockerfile.in
@@ -0,0 +1,18 @@
+# Copyright 2015 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM ${BASE_IMAGE}
+
+ENV DOCUMENT_ROOT=/app/web
+ENV FRONT_CONTROLLER_FILE=app.php
diff --git a/testapps/php74_custom/composer.json b/testapps/php74_custom/composer.json
new file mode 100644
index 00000000..21d1038d
--- /dev/null
+++ b/testapps/php74_custom/composer.json
@@ -0,0 +1,22 @@
+{
+ "require": {
+ "php": "7.4.*",
+ "ext-bcmath": "*",
+ "ext-calendar": "*",
+ "ext-exif": "*",
+ "ext-ftp": "*",
+ "ext-gd": "*",
+ "ext-gettext": "*",
+ "ext-intl": "*",
+ "ext-shmop": "*",
+ "ext-soap": "*",
+ "ext-sqlite3": "*",
+ "ext-pdo_sqlite": "*",
+ "ext-xmlrpc": "*",
+ "ext-xsl": "*",
+ "ext-mongodb": "*",
+ "ext-redis": "*",
+ "ext-imagick": "*",
+ "silex/silex": "^1.3"
+ }
+}
diff --git a/testapps/php74_custom/php74.yaml b/testapps/php74_custom/php74.yaml
new file mode 100644
index 00000000..f71ecd8a
--- /dev/null
+++ b/testapps/php74_custom/php74.yaml
@@ -0,0 +1,5 @@
+schemaVersion: "1.0.0"
+commandTests:
+ - name: "version"
+ command: ["/opt/php/bin/php", "-v"]
+ expectedOutput: ["PHP 7\\.4.*"]
diff --git a/testapps/php74_custom/tests/composer.json b/testapps/php74_custom/tests/composer.json
new file mode 100644
index 00000000..c3a35663
--- /dev/null
+++ b/testapps/php74_custom/tests/composer.json
@@ -0,0 +1,6 @@
+{
+ "require-dev": {
+ "guzzlehttp/guzzle": "~6.0",
+ "symfony/browser-kit": "~2"
+ }
+}
diff --git a/testapps/php74_custom/tests/phpunit.xml.dist b/testapps/php74_custom/tests/phpunit.xml.dist
new file mode 100644
index 00000000..8ded8984
--- /dev/null
+++ b/testapps/php74_custom/tests/phpunit.xml.dist
@@ -0,0 +1,8 @@
+
+
+
+
+ tests
+
+
+
diff --git a/testapps/php74_custom/tests/tests/PHP74CustomTest.php b/testapps/php74_custom/tests/tests/PHP74CustomTest.php
new file mode 100644
index 00000000..bc5e9431
--- /dev/null
+++ b/testapps/php74_custom/tests/tests/PHP74CustomTest.php
@@ -0,0 +1,139 @@
+client = new Client(['base_uri' => 'http://php74-custom:8080/']);
+ }
+
+ public function testExtensions()
+ {
+ $resp = $this->client->get('extensions.php');
+ $loaded = $resp->getBody()->getContents();
+ foreach (self::$extensions as $ext) {
+ $this->assertContains($ext, $loaded);
+ }
+ }
+
+ public function testApcIsAbleToExecuteCommonOperations()
+ {
+ $resp = $this->client->get('apc.php');
+ $body = $resp->getBody()->getContents();
+
+ $this->assertContains('success storing in apc bc', $body);
+ $this->assertContains('success fetching from apc bc', $body);
+ $this->assertContains('success deleting from apc bc', $body);
+ $this->assertContains('success storing in apcu', $body);
+ $this->assertContains('success fetching from apcu', $body);
+ $this->assertContains('success deleting from apcu', $body);
+ }
+
+ public function testImagickCanLoad()
+ {
+ $resp = $this->client->get('imagick.php');
+ $body = $resp->getBody()->getContents();
+
+ // test image should by 300px by 1px
+ $this->assertContains('300x1', $body);
+ }
+
+ public function testFrontControllerFileEnv()
+ {
+ // Access the top page and it should be served by app.php
+ $resp = $this->client->get('');
+ $body = $resp->getBody()->getContents();
+ $this->assertContains('FRONT_CONTROLLER_FILE works', $body);
+ }
+}
diff --git a/testapps/php74_custom/tests/tests/bootstrap.php b/testapps/php74_custom/tests/tests/bootstrap.php
new file mode 100644
index 00000000..864f1cd1
--- /dev/null
+++ b/testapps/php74_custom/tests/tests/bootstrap.php
@@ -0,0 +1,17 @@
+ $value) {
+ echo $value . "\n";
+}
diff --git a/testapps/php74_custom/web/imagick.php b/testapps/php74_custom/web/imagick.php
new file mode 100644
index 00000000..59ed28fb
--- /dev/null
+++ b/testapps/php74_custom/web/imagick.php
@@ -0,0 +1,21 @@
+getImageGeometry();
+echo $dimensions['width'] . 'x' . $dimensions['height'];
diff --git a/testapps/php74_custom/web/parse_str.php b/testapps/php74_custom/web/parse_str.php
new file mode 100644
index 00000000..978b35d1
--- /dev/null
+++ b/testapps/php74_custom/web/parse_str.php
@@ -0,0 +1,23 @@
+
+
+
+
+ tests
+
+
+
diff --git a/testapps/php74_e2e/tests/tests/EndToEndTest.php b/testapps/php74_e2e/tests/tests/EndToEndTest.php
new file mode 100644
index 00000000..2260dfa2
--- /dev/null
+++ b/testapps/php74_e2e/tests/tests/EndToEndTest.php
@@ -0,0 +1,250 @@
+eventuallyConsistentRetryCount = 10;
+ $this->catchAllExceptions = true;
+
+ $url = sprintf('https://%s-dot-%s.appspot.com/',
+ getenv(self::VERSION_ENV),
+ getenv(self::PROJECT_ENV));
+ $this->client = new Client(['base_uri' => $url]);
+ }
+
+ public function testIndex()
+ {
+ $this->runEventuallyConsistentTest(function () {
+ // Index serves succesfully with 'Hello World'.
+ // This works because the custom DOCUMENT_ROOT is working.
+ $resp = $this->client->get('index.php');
+ $this->assertEquals('200', $resp->getStatusCode(),
+ 'index.php status code');
+ $this->assertContains('Hello World', $resp->getBody()->getContents());
+ });
+ }
+
+ public function testHttpsEnv()
+ {
+ $this->runEventuallyConsistentTest(function () {
+ // Check the HTTPS envvar on the server
+ $resp = $this->client->get('https-env.php');
+ $this->assertEquals('200', $resp->getStatusCode(),
+ 'https-env.php status code');
+ $this->assertContains('HTTPS: on', $resp->getBody()->getContents());
+ });
+ }
+
+ public function testGoodbye()
+ {
+ $this->runEventuallyConsistentTest(function () {
+ // The URL '/goodbye' works with 'Goodbye World'.
+ // This works because the nginx-app.conf is effective.
+ $resp = $this->client->get('/goodbye');
+ $this->assertEquals('200', $resp->getStatusCode(),
+ '/goodbye status code');
+ $this->assertContains('Goodbye World',
+ $resp->getBody()->getContents());
+ });
+ }
+
+ public function testPhpInfo()
+ {
+ $this->runEventuallyConsistentTest(function () {
+ // Access to phpinfo.php, while phpinfo() should be enabled this time.
+ $resp = $this->client->get('phpinfo.php');
+ $this->assertEquals('200', $resp->getStatusCode(),
+ 'phpinfo.php status code');
+ $this->assertTrue(strlen($resp->getBody()->getContents()) > 1000,
+ 'phpinfo() should be enabled.');
+ });
+ }
+
+ public function testExec()
+ {
+ $this->runEventuallyConsistentTest(function () {
+ // Access to exec.php; exec() should be enabled.
+ $resp = $this->client->get('exec.php');
+ $this->assertEquals(
+ '200',
+ $resp->getStatusCode(),
+ 'exec.php status code'
+ );
+ $this->assertContains(
+ 'exec succeeded.',
+ $resp->getBody()->getContents()
+ );
+ });
+ }
+
+ public function testPdoSqlite()
+ {
+ $this->runEventuallyConsistentTest(function () {
+ // Access to pdo_sqlite.php, which should work.
+ $resp = $this->client->get('pdo_sqlite.php');
+ $this->assertEquals('200', $resp->getStatusCode(),
+ 'pdo_sqlite.php status code');
+ $this->assertContains('Hello pdo_sqlite',
+ $resp->getBody()->getContents());
+ });
+ }
+
+ public function testSessionSaveHandler()
+ {
+ $this->markTestSkipped('Memcache is not available on env:flex.');
+ $this->runEventuallyConsistentTest(function () {
+ $resp = $this->client->get('session_save_handler.php');
+ $this->assertEquals('200', $resp->getStatusCode(),
+ 'session_save_handler status code');
+ $this->assertContains('memcached',
+ $resp->getBody()->getContents());
+ });
+ }
+
+ public function testSession()
+ {
+ $this->markTestSkipped('Memcache is not available on env:flex.');
+ $this->runEventuallyConsistentTest(function () {
+ $jar = new CookieJar();
+ $resp = $this->client->get('session.php', ['cookies' => $jar]);
+ $this->assertEquals('200', $resp->getStatusCode(),
+ 'session.php status code');
+ $this->assertEquals('0', $body = $resp->getBody()->getContents());
+
+ $resp = $this->client->get('session.php', ['cookies' => $jar]);
+ $this->assertEquals('200', $resp->getStatusCode(),
+ 'session.php status code');
+ $this->assertEquals('1', $body = $resp->getBody()->getContents());
+ });
+ }
+
+ public function testGrpcPubsub()
+ {
+ $this->runEventuallyConsistentTest(function () {
+ $resp = $this->client->get('grpc_pubsub.php');
+ $this->assertEquals('200', $resp->getStatusCode(),
+ 'grpc_pubsub.php status code');
+ });
+ }
+}
diff --git a/testapps/php74_e2e/tests/tests/bootstrap.php b/testapps/php74_e2e/tests/tests/bootstrap.php
new file mode 100644
index 00000000..823a5ebe
--- /dev/null
+++ b/testapps/php74_e2e/tests/tests/bootstrap.php
@@ -0,0 +1,18 @@
+ 'grpc']);
+
+foreach ($client->topics() as $topic) {
+ echo $topic->name() . '
';
+}
diff --git a/testapps/php74_e2e/web/https-env.php b/testapps/php74_e2e/web/https-env.php
new file mode 100644
index 00000000..fb430257
--- /dev/null
+++ b/testapps/php74_e2e/web/https-env.php
@@ -0,0 +1,19 @@
+get('/', function () {
+ return 'Hello World';
+});
+
+$app->get('/goodbye', function () {
+ return 'Goodbye World';
+});
+
+$app->run();
diff --git a/testapps/php74_e2e/web/pdo_sqlite.php b/testapps/php74_e2e/web/pdo_sqlite.php
new file mode 100644
index 00000000..5b800090
--- /dev/null
+++ b/testapps/php74_e2e/web/pdo_sqlite.php
@@ -0,0 +1,19 @@
+
+
+
+
+ tests
+
+
+
diff --git a/testapps/php74_extensions/tests/Blank300.png b/testapps/php74_extensions/tests/Blank300.png
new file mode 100644
index 00000000..e96fe466
Binary files /dev/null and b/testapps/php74_extensions/tests/Blank300.png differ
diff --git a/testapps/php74_extensions/tests/EvTest.php b/testapps/php74_extensions/tests/EvTest.php
new file mode 100644
index 00000000..cd556d33
--- /dev/null
+++ b/testapps/php74_extensions/tests/EvTest.php
@@ -0,0 +1,43 @@
+assertTrue(extension_loaded('ev'));
+ }
+
+ public function testTimer()
+ {
+ // after 5ms, update success
+ $w1 = new EvTimer(0.005, 0, function () {
+ $this->success = true;
+ });
+ Ev::run();
+
+ // sleep 10ms
+ usleep(10000);
+
+ Ev::stop();
+
+ $this->assertTrue($this->success);
+ }
+}
diff --git a/testapps/php74_extensions/tests/EventTest.php b/testapps/php74_extensions/tests/EventTest.php
new file mode 100644
index 00000000..1fe6535f
--- /dev/null
+++ b/testapps/php74_extensions/tests/EventTest.php
@@ -0,0 +1,27 @@
+assertTrue(extension_loaded('event'));
+ }
+}
diff --git a/testapps/php74_extensions/tests/ExtensionsLoadedTest.php b/testapps/php74_extensions/tests/ExtensionsLoadedTest.php
new file mode 100644
index 00000000..6bf2f3f4
--- /dev/null
+++ b/testapps/php74_extensions/tests/ExtensionsLoadedTest.php
@@ -0,0 +1,57 @@
+assertTrue(extension_loaded($extensionName));
+ }
+
+ public function extensions()
+ {
+ return [
+ ['amqp'],
+ ['bitset'],
+ ['couchbase'],
+ ['ds'],
+ ['eio'],
+ ['hprose'],
+ ['igbinary'],
+ ['jsond'],
+ ['krb5'],
+ ['lua'],
+ ['lzf'],
+ ['memprof'],
+ ['opencensus'],
+ ['seaslog'],
+ ['stackdriver_debugger'],
+ ['stomp'],
+ ['sync'],
+ ['tcpwrap'],
+ ['timezonedb'],
+ ['vips'],
+ ['yaconf'],
+ ['yaf'],
+ ['yaml']
+ ];
+ }
+}
diff --git a/testapps/php74_extensions/tests/GdTest.php b/testapps/php74_extensions/tests/GdTest.php
new file mode 100644
index 00000000..c663b508
--- /dev/null
+++ b/testapps/php74_extensions/tests/GdTest.php
@@ -0,0 +1,33 @@
+assertTrue(extension_loaded('gd'));
+ }
+
+ public function testFreetypeSupport()
+ {
+ $this->assertTrue(
+ gd_info()['FreeType Support'],
+ 'GD should support Freetype'
+ );
+ }
+}
diff --git a/testapps/php74_extensions/tests/GmpTest.php b/testapps/php74_extensions/tests/GmpTest.php
new file mode 100644
index 00000000..ed7e0821
--- /dev/null
+++ b/testapps/php74_extensions/tests/GmpTest.php
@@ -0,0 +1,34 @@
+assertTrue(extension_loaded('gmp'));
+ }
+
+ public function testGcd()
+ {
+ // basic test for greatest common denominator to see if gmp works
+ $this->assertEquals(
+ 3,
+ gmp_intval(gmp_gcd("12", "21"))
+ );
+ }
+}
diff --git a/testapps/php74_extensions/tests/ImagickTest.php b/testapps/php74_extensions/tests/ImagickTest.php
new file mode 100644
index 00000000..4670b141
--- /dev/null
+++ b/testapps/php74_extensions/tests/ImagickTest.php
@@ -0,0 +1,35 @@
+assertTrue(extension_loaded('imagick'));
+ }
+
+ public function testLoadImage()
+ {
+ $image = new Imagick(__DIR__ . '/Blank300.png');
+ $dimensions = $image->getImageGeometry();
+ $this->assertEquals(300, $dimensions['width']);
+ $this->assertEquals(1, $dimensions['height']);
+ }
+}
diff --git a/testapps/php74_extensions/tests/OauthTest.php b/testapps/php74_extensions/tests/OauthTest.php
new file mode 100644
index 00000000..f0fa2572
--- /dev/null
+++ b/testapps/php74_extensions/tests/OauthTest.php
@@ -0,0 +1,27 @@
+assertTrue(extension_loaded('oauth'));
+ }
+}
diff --git a/testapps/php74_extensions/tests/PhalconTest.php b/testapps/php74_extensions/tests/PhalconTest.php
new file mode 100644
index 00000000..84ed26d1
--- /dev/null
+++ b/testapps/php74_extensions/tests/PhalconTest.php
@@ -0,0 +1,28 @@
+markTestSkipped('Phalcon is not available for PHP 7.1 yet.');
+ // $this->assertTrue(extension_loaded('phalcon'));
+ }
+}
diff --git a/testapps/php74_extensions/tests/ProtobufTest.php b/testapps/php74_extensions/tests/ProtobufTest.php
new file mode 100644
index 00000000..0d9606e5
--- /dev/null
+++ b/testapps/php74_extensions/tests/ProtobufTest.php
@@ -0,0 +1,27 @@
+assertTrue(extension_loaded('protobuf'));
+ }
+}
diff --git a/testapps/php74_extensions/tests/RdkafkaTest.php b/testapps/php74_extensions/tests/RdkafkaTest.php
new file mode 100644
index 00000000..2e522a4e
--- /dev/null
+++ b/testapps/php74_extensions/tests/RdkafkaTest.php
@@ -0,0 +1,27 @@
+assertTrue(extension_loaded('rdkafka'));
+ }
+}
diff --git a/testapps/php74_extensions/tests/bootstrap.php b/testapps/php74_extensions/tests/bootstrap.php
new file mode 100644
index 00000000..f76545c7
--- /dev/null
+++ b/testapps/php74_extensions/tests/bootstrap.php
@@ -0,0 +1,17 @@
+
+
+
+
+ tests
+
+
+
diff --git a/testapps/php74_extensions_legacy/tests/Blank300.png b/testapps/php74_extensions_legacy/tests/Blank300.png
new file mode 100644
index 00000000..e96fe466
Binary files /dev/null and b/testapps/php74_extensions_legacy/tests/Blank300.png differ
diff --git a/testapps/php74_extensions_legacy/tests/EvTest.php b/testapps/php74_extensions_legacy/tests/EvTest.php
new file mode 100644
index 00000000..cd556d33
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/EvTest.php
@@ -0,0 +1,43 @@
+assertTrue(extension_loaded('ev'));
+ }
+
+ public function testTimer()
+ {
+ // after 5ms, update success
+ $w1 = new EvTimer(0.005, 0, function () {
+ $this->success = true;
+ });
+ Ev::run();
+
+ // sleep 10ms
+ usleep(10000);
+
+ Ev::stop();
+
+ $this->assertTrue($this->success);
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/EventTest.php b/testapps/php74_extensions_legacy/tests/EventTest.php
new file mode 100644
index 00000000..1fe6535f
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/EventTest.php
@@ -0,0 +1,27 @@
+assertTrue(extension_loaded('event'));
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/ExtensionsLoadedTest.php b/testapps/php74_extensions_legacy/tests/ExtensionsLoadedTest.php
new file mode 100644
index 00000000..6bf2f3f4
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/ExtensionsLoadedTest.php
@@ -0,0 +1,57 @@
+assertTrue(extension_loaded($extensionName));
+ }
+
+ public function extensions()
+ {
+ return [
+ ['amqp'],
+ ['bitset'],
+ ['couchbase'],
+ ['ds'],
+ ['eio'],
+ ['hprose'],
+ ['igbinary'],
+ ['jsond'],
+ ['krb5'],
+ ['lua'],
+ ['lzf'],
+ ['memprof'],
+ ['opencensus'],
+ ['seaslog'],
+ ['stackdriver_debugger'],
+ ['stomp'],
+ ['sync'],
+ ['tcpwrap'],
+ ['timezonedb'],
+ ['vips'],
+ ['yaconf'],
+ ['yaf'],
+ ['yaml']
+ ];
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/GdTest.php b/testapps/php74_extensions_legacy/tests/GdTest.php
new file mode 100644
index 00000000..c663b508
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/GdTest.php
@@ -0,0 +1,33 @@
+assertTrue(extension_loaded('gd'));
+ }
+
+ public function testFreetypeSupport()
+ {
+ $this->assertTrue(
+ gd_info()['FreeType Support'],
+ 'GD should support Freetype'
+ );
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/GmpTest.php b/testapps/php74_extensions_legacy/tests/GmpTest.php
new file mode 100644
index 00000000..ed7e0821
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/GmpTest.php
@@ -0,0 +1,34 @@
+assertTrue(extension_loaded('gmp'));
+ }
+
+ public function testGcd()
+ {
+ // basic test for greatest common denominator to see if gmp works
+ $this->assertEquals(
+ 3,
+ gmp_intval(gmp_gcd("12", "21"))
+ );
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/ImagickTest.php b/testapps/php74_extensions_legacy/tests/ImagickTest.php
new file mode 100644
index 00000000..4670b141
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/ImagickTest.php
@@ -0,0 +1,35 @@
+assertTrue(extension_loaded('imagick'));
+ }
+
+ public function testLoadImage()
+ {
+ $image = new Imagick(__DIR__ . '/Blank300.png');
+ $dimensions = $image->getImageGeometry();
+ $this->assertEquals(300, $dimensions['width']);
+ $this->assertEquals(1, $dimensions['height']);
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/LibSodiumTest.php b/testapps/php74_extensions_legacy/tests/LibSodiumTest.php
new file mode 100644
index 00000000..9f2ac230
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/LibSodiumTest.php
@@ -0,0 +1,32 @@
+assertTrue(extension_loaded('sodium'));
+ }
+
+ public function testLoadImage()
+ {
+ $re = new ReflectionExtension('sodium');
+ $libsodiumVersion = $re->getVersion();
+ $this->assertTrue(version_compare($libsodiumVersion, '2.0.0', '>='));
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/OauthTest.php b/testapps/php74_extensions_legacy/tests/OauthTest.php
new file mode 100644
index 00000000..f0fa2572
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/OauthTest.php
@@ -0,0 +1,27 @@
+assertTrue(extension_loaded('oauth'));
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/PhalconTest.php b/testapps/php74_extensions_legacy/tests/PhalconTest.php
new file mode 100644
index 00000000..84ed26d1
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/PhalconTest.php
@@ -0,0 +1,28 @@
+markTestSkipped('Phalcon is not available for PHP 7.1 yet.');
+ // $this->assertTrue(extension_loaded('phalcon'));
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/ProtobufTest.php b/testapps/php74_extensions_legacy/tests/ProtobufTest.php
new file mode 100644
index 00000000..0d9606e5
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/ProtobufTest.php
@@ -0,0 +1,27 @@
+assertTrue(extension_loaded('protobuf'));
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/RdkafkaTest.php b/testapps/php74_extensions_legacy/tests/RdkafkaTest.php
new file mode 100644
index 00000000..2e522a4e
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/RdkafkaTest.php
@@ -0,0 +1,27 @@
+assertTrue(extension_loaded('rdkafka'));
+ }
+}
diff --git a/testapps/php74_extensions_legacy/tests/bootstrap.php b/testapps/php74_extensions_legacy/tests/bootstrap.php
new file mode 100644
index 00000000..f76545c7
--- /dev/null
+++ b/testapps/php74_extensions_legacy/tests/bootstrap.php
@@ -0,0 +1,17 @@
+