Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,9 @@

from __future__ import annotations

import time

import pytest


def wait_for_http(container_exec_shell, url: str):
"""Poll until an HTTP endpoint responds successfully."""
result = None
for _ in range(5):
result = container_exec_shell(f"curl -sf {url}")
if result.exit_code == 0:
return result
time.sleep(1)

assert result is not None
return result


@pytest.mark.dockerfile()
def test_nginx_config_valid(container_exec_shell) -> None:
"""nginx configuration must pass validation."""
Expand All @@ -35,11 +20,6 @@ def test_nginx_config_valid(container_exec_shell) -> None:


@pytest.mark.dockerfile()
def test_nginx_health_endpoint(container_exec_shell) -> None:
def test_nginx_health_endpoint(assert_http_server) -> None:
"""nginx /health endpoint must return 200."""
start = container_exec_shell("nginx")
assert start.exit_code == 0, f"nginx failed to start: {start.output}"

result = wait_for_http(container_exec_shell, "http://localhost:80/health")
assert result.exit_code == 0, f"health check failed: {result.output}"
assert "healthy" in result.output
assert_http_server("nginx", "http://localhost:80/health", "healthy")
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
RUN dnf install -y nodejs curl && dnf clean all
COPY server.js /app/server.js
COPY response.txt /app/response.txt
EXPOSE 8080
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from the node server running from Azure Linux NodeJS container.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

// Minimal stdlib HTTP server used to validate the Node.js runtime.
const http = require('http');
const fs = require('fs');
const path = require('path');

const PORT = 8080;
const HOST = '0.0.0.0';
const RESPONSE = fs.readFileSync(path.join(__dirname, 'response.txt'));

const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(RESPONSE);
});

server.listen(PORT, HOST);
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: MIT
"""Validate the Node.js runtime works on the container-base image.

Uses ``@pytest.mark.dockerfile()`` to build a custom image with Node.js
installed on top of the image-under-test, then runs a stdlib ``http``
server and checks its response.
"""

from __future__ import annotations

from pathlib import Path

import pytest

EXPECTED_RESPONSE = (Path(__file__).with_name("response.txt")).read_text().strip()


@pytest.mark.dockerfile()
def test_nodejs_version(container_exec_shell) -> None:
"""Node.js interpreter must be present and report a version."""
result = container_exec_shell("node --version")
assert result.exit_code == 0, f"node --version failed: {result.output}"
assert result.output.strip().startswith("v")


@pytest.mark.dockerfile()
def test_nodejs_http_server(assert_http_server) -> None:
"""A stdlib http server must serve the expected response."""
assert_http_server(
"nohup node /app/server.js > /tmp/server.log 2>&1 &",
"http://localhost:8080/",
EXPECTED_RESPONSE,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
RUN dnf install -y php-cli php-pecl-zip unzip curl && dnf clean all
COPY router.php /app/router.php
COPY response.txt /app/response.txt
EXPOSE 8080
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from the PHP server running from Azure Linux PHP container. Saves zip file correctly.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
// router.php
// Exercises the php-pecl-zip extension by creating, writing and
// extracting a zip archive, then verifying the round-trip.
if (preg_match('/\.(?:png|jpg|jpeg|gif)$/', $_SERVER["REQUEST_URI"])) {
return false; // serve the requested resource as-is.
} else {
$zip = new ZipArchive();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question(non-blocking): I'm wondering what motivated the more extensive testing (zip/unzip) for php and why you didn't do something similar for the other languages.

@bhagyapathak bhagyapathak Jul 2, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @tobias for the review.
test_php_zip_extension_loaded is intentionally PHP-specific. This is a direct port of the existing golden-container smoke tests, and my current goal is to bring them to the Azure Linux 4.0 base container with minimal changes. Once the golden containers are available, I plan to run the same test suite against them as well. For that reason, I have kept the tests as close to the original implementation as possible. But I am open to the option of adding this test later when golden container is availble.

The ZIP validation originates from the upstream PHP golden-container test. The php-pecl-zip package is an optional extension, so the presence of PHP alone does not guarantee that the ZIP extension is available. That's why the test explicitly verifies it through both a php -m check and a functional round-trip validation.

The other runtimes do not have an equivalent optional, headless-relevant extension.

$testFolder = shell_exec("mktemp -d | tr -d '\n'");
$filename = "$testFolder/test_archive.zip";
if ($zip->open($filename, ZipArchive::CREATE) !== TRUE) {
exit("cannot open <$filename>\n");
}
$zip->addFromString("testfilephp.txt", "#1 This is a test string added as testfilephp.txt.\n");
$zip->addFromString("testfilephp2.txt", "#2 This is a test string added as testfilephp2.txt.\n");
$zip->addFile("/app/router.php", "router.php");
$zip->close();
if (file_exists($filename)) {
shell_exec("unzip $filename -d $testFolder/test_folder");
foreach (array("testfilephp.txt", "testfilephp2.txt", "router.php") as $testFile) {
if (!file_exists("$testFolder/test_folder/$testFile")) return false;
}
shell_exec("rm -rf $testFolder");
echo file_get_contents(__DIR__ . "/response.txt");
} else {
exit("Zip archive not created, server reached though.");
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: MIT
"""Validate the PHP runtime works on the container-base image.

Uses ``@pytest.mark.dockerfile()`` to build a custom image with PHP and
the zip extension installed on top of the image-under-test, then runs
the built-in PHP web server and verifies a zip round-trip via router.php.
"""

from __future__ import annotations

from pathlib import Path

import pytest

EXPECTED_RESPONSE = (Path(__file__).with_name("response.txt")).read_text().strip()


@pytest.mark.dockerfile()
def test_php_version(container_exec_shell) -> None:
"""PHP interpreter must be present and report a version."""
result = container_exec_shell("php --version")
assert result.exit_code == 0, f"php --version failed: {result.output}"
assert "PHP" in result.output


@pytest.mark.dockerfile()
def test_php_zip_extension_loaded(container_exec_shell) -> None:
"""The zip extension must be loaded in the PHP runtime."""
result = container_exec_shell("php -m")
assert result.exit_code == 0, f"php -m failed: {result.output}"
assert "zip" in result.output


@pytest.mark.dockerfile()
def test_php_http_server(assert_http_server) -> None:
"""The built-in PHP server must serve a successful zip round-trip."""
assert_http_server(
"nohup php -S 0.0.0.0:8080 /app/router.php > /tmp/server.log 2>&1 &",
"http://localhost:8080/",
EXPECTED_RESPONSE,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
RUN dnf install -y python3 curl && dnf clean all
COPY app.py /app/app.py
COPY response.txt /app/response.txt
EXPOSE 8080
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Minimal stdlib HTTP server used to validate the Python runtime."""

from http.server import BaseHTTPRequestHandler, HTTPServer
Comment thread
Copilot marked this conversation as resolved.
from pathlib import Path

RESPONSE = Path(__file__).with_name("response.txt").read_bytes()


class Handler(BaseHTTPRequestHandler):
def do_GET(self): # noqa: N802 - http.server API
Comment thread
Copilot marked this conversation as resolved.
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(RESPONSE)


if __name__ == "__main__":
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from the python server running from Azure Linux Python container.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: MIT
"""Validate the Python runtime works on the container-base image.

Uses ``@pytest.mark.dockerfile()`` to build a custom image with the
Python interpreter installed on top of the image-under-test, then runs
a stdlib ``http.server`` app and checks its response.
"""

from __future__ import annotations

from pathlib import Path

import pytest

EXPECTED_RESPONSE = (Path(__file__).with_name("response.txt")).read_text().strip()


@pytest.mark.dockerfile()
def test_python_version(container_exec_shell) -> None:
"""Python interpreter must be present and report version 3."""
result = container_exec_shell("python3 --version")
assert result.exit_code == 0, f"python3 --version failed: {result.output}"
assert "Python 3" in result.output


@pytest.mark.dockerfile()
def test_python_http_server(assert_http_server) -> None:
"""A stdlib http.server app must serve the expected response."""
assert_http_server(
"nohup python3 /app/app.py > /tmp/server.log 2>&1 &",
"http://localhost:8080/",
EXPECTED_RESPONSE,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
RUN dnf install -y ruby curl && dnf clean all
COPY app.rb /app/app.rb
COPY response.txt /app/response.txt
EXPOSE 8080
19 changes: 19 additions & 0 deletions base/images/tests/cases/runtime/container-base/test_ruby/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Minimal stdlib TCP HTTP server used to validate the Ruby runtime.
require 'socket'

server = TCPServer.new('0.0.0.0', 8080)
body = File.read(File.join(__dir__, 'response.txt'))

trap("INT") { server.close; exit }

loop do
client = server.accept
client.gets
client.print "HTTP/1.1 200 OK\r\n"
client.print "Content-Type: text/plain\r\n"
client.print "Content-Length: #{body.bytesize}\r\n"
client.print "Connection: close\r\n"
client.print "\r\n"
client.print body
client.close
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from Ruby
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: MIT
"""Validate the Ruby runtime works on the container-base image.

Uses ``@pytest.mark.dockerfile()`` to build a custom image with Ruby
installed on top of the image-under-test, then runs a stdlib socket
HTTP server and checks its response.
"""

from __future__ import annotations

from pathlib import Path

import pytest

EXPECTED_RESPONSE = (Path(__file__).with_name("response.txt")).read_text().strip()


@pytest.mark.dockerfile()
def test_ruby_version(container_exec_shell) -> None:
"""Ruby interpreter must be present and report a version."""
result = container_exec_shell("ruby --version")
assert result.exit_code == 0, f"ruby --version failed: {result.output}"
assert "ruby" in result.output


@pytest.mark.dockerfile()
def test_ruby_http_server(assert_http_server) -> None:
"""A stdlib socket HTTP server must serve the expected response."""
assert_http_server(
"nohup ruby /app/app.rb > /tmp/server.log 2>&1 &",
"http://localhost:8080/",
EXPECTED_RESPONSE,
)
Loading
Loading