diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f63827ed9..8b5073055 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ env: jobs: test: name: Regression Test ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.name || inputs.name }} ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.target || inputs.target }} - runs-on: [ self-hosted, regression ] + runs-on: [self-hosted, regression] steps: - name: Checkout infix repo uses: actions/checkout@v4 @@ -87,7 +87,7 @@ jobs: fi make test - - name: Publish Test Result for ${{ env.TARGET }} + - name: Publish Test Result for ${{ env.TARGET }} # Ensure this runs even if Regression Test fails if: always() run: cat $TEST_PATH/.log/last/result-gh.md >> $GITHUB_STEP_SUMMARY @@ -96,15 +96,10 @@ jobs: # Ensure this runs even if Regression Test fails if: always() run: | - asciidoctor-pdf \ - --theme $TEST_PATH/9pm/report/theme.yml \ - -a pdf-fontsdir=$TEST_PATH/9pm/report/fonts \ - $TEST_PATH/.log/last/report.adoc \ - -o $TEST_PATH/.log/last/report.pdf + make test-dir="$(pwd)/$TEST_PATH" test-report - name: Upload Test Report as Artifact uses: actions/upload-artifact@v4 with: name: test-report - path: ${{ env.TEST_PATH }}/.log/last/report.pdf - + path: output/images/test-report.pdf diff --git a/.github/workflows/trigger.yml b/.github/workflows/trigger.yml index ed1f274da..de8076b47 100644 --- a/.github/workflows/trigger.yml +++ b/.github/workflows/trigger.yml @@ -37,6 +37,6 @@ jobs: name: "infix" test-publish-x86_64: - if: startsWith(github.repository, 'kernelkit/') + if: ${{ github.repository_owner == 'kernelkit' && github.ref_name == 'main' }} needs: test-run-x86_64 uses: ./.github/workflows/publish.yml diff --git a/board/common/mkdisk.sh b/board/common/mkdisk.sh index ababde0fa..dfb472b34 100755 --- a/board/common/mkdisk.sh +++ b/board/common/mkdisk.sh @@ -83,7 +83,7 @@ genboot() { if [ -d "$bootdata" ]; then bootimg=$(cat < - - + + 2x2 - + PC - -PC - + +PC + mgmt1 data mgmt2 + +mgmt3 R1 - -mgmt - -data - -link - -R1 - 192.168.100.1/32 -(lo) + +mgmt + +data + +link + +R1 + 192.168.100.1/32 +(lo) PC:mgmt1--R1:mgmt - + - + PC:data--R1:data - -192.168.10.1/24 -192.168.10.2/24 + +192.168.10.1/24 +192.168.10.2/24 R2 - -link - -mgmt - -data - -R2 - 192.168.200.1/32 -(lo) + +link + +mgmt + +data + +R2 + 192.168.200.1/32 +(lo) PC:mgmt2--R2:mgmt - + + + + +HOST + +link + +mgmt + +HOST + end-device + + + +PC:mgmt3--HOST:mgmt + - + R1:link--R2:link - -192.168.50.2/24 -192.168.50.1/24 + +192.168.50.2/24 +192.168.50.1/24 + + + +R2:data--HOST:link + +192.168.60.2/24 +192.168.60.1/24 diff --git a/test/case/infix_containers/container_enabled/container_enabled.adoc b/test/case/infix_containers/container_enabled/container_enabled.adoc index 69b698fae..45614ed6d 100644 --- a/test/case/infix_containers/container_enabled/container_enabled.adoc +++ b/test/case/infix_containers/container_enabled/container_enabled.adoc @@ -26,7 +26,6 @@ endif::topdoc[] . Set hostname to 'container-host' . Create enabled container from bundled OCI image . Verify container has started -. Let container settle . Disable container . Verify container has stopped . Re-enable container diff --git a/test/case/infix_containers/container_environment/container_environment.adoc b/test/case/infix_containers/container_environment/container_environment.adoc index d7050ef4b..4634ce7ef 100644 --- a/test/case/infix_containers/container_environment/container_environment.adoc +++ b/test/case/infix_containers/container_environment/container_environment.adoc @@ -1,14 +1,11 @@ === Container environment variables ==== Description Verify that environment variables can be set in container configuration -and are available inside the running container. Tests the 'env' list -functionality by: +and are available inside the running container. -1. Creating a container with multiple environment variables -2. Using a custom script to extract env vars and serve them via HTTP -3. Fetching the served content to verify environment variables are set correctly - -Uses the nftables container image with custom rc.local script. +1 Set up a container config with multiple environment variables +2. Serve variables back to host using a CGI script in container +3. Verify served content against environment variables ==== Topology ifdef::topdoc[] @@ -25,11 +22,10 @@ endif::topdoc[] ==== Test sequence . Set up topology and attach to target DUT . Configure data interface with static IPv4 -. Create container with environment variables +. Set up container with environment variables . Verify container has started -. Verify environment variables are available via HTTP . Verify basic connectivity to data interface -. Verify environment variables in HTTP response +. Verify environment variables in CGI response <<< diff --git a/test/infamy/furl.py b/test/infamy/furl.py index 28b286914..bfb40bd48 100644 --- a/test/infamy/furl.py +++ b/test/infamy/furl.py @@ -1,26 +1,72 @@ -"""Fugly URL fetcher""" +"""Simple HTTP URL fetcher and content checker + +This module provides the a lightweight wrapper around urllib for testing +HTTP endpoints. It's designed specifically for test scenarios where you +need to: + +- Fetch HTTP content and verify it contains expected strings +- Perform checks from within network namespaces +- Validate multiple content patterns in a single request +- Handle network errors gracefully in test environments + +Example usage: + # Single needle check + url = Furl("http://example.com/api") + if url.check("success"): + print("API returned success") + + # Multiple needle check (all must match) + if url.check(["user: alice", "status: active", "role: admin"]): + print("All user details found") + + # Check from network namespace + with IsolatedMacVlan("eth0") as ns: + if url.nscheck(ns, "expected content"): + print("Content verified from namespace") +""" import urllib.error +import urllib.parse import urllib.request + class Furl: """Furl wraps urllib in a way similar to curl""" def __init__(self, url): """Create new URL checker""" self.url = urllib.parse.quote(url, safe='/:') - def check(self, needle, timeout=10): - """Connect to web server URL, fetch body and check for needle""" + def check(self, needles, timeout=10): + """Connect to web server URL, fetch body and check for needle(s) + + Args: + needles: String or list of strings to search for in response + timeout: Request timeout in seconds + + Returns: + bool: True if all needles found in response, False otherwise + """ + # Backwards compat, make needles a list for uniform processing + if isinstance(needles, str): + needles = [needles] + try: with urllib.request.urlopen(self.url, timeout=timeout) as response: text = response.read().decode('utf-8') - #print(text) - return needle in text - except urllib.error.URLError as _: + return all(needle in text for needle in needles) + except urllib.error.URLError: return False except ConnectionResetError: return False - def nscheck(self, netns, needle): - """"Call check() from netns""" - return netns.call(lambda: self.check(needle)) + def nscheck(self, netns, needles): + """"Call check() from netns + + Args: + netns: Network namespace to call from + needles: String or list of strings to search for in response + + Returns: + bool: True if all needles found in response, False otherwise + """ + return netns.call(lambda: self.check(needles)) diff --git a/test/infamy/restconf.py b/test/infamy/restconf.py index 44d9de041..310788360 100644 --- a/test/infamy/restconf.py +++ b/test/infamy/restconf.py @@ -150,7 +150,6 @@ def get_schema(self, name, revision, yangdir): url = f"{self.yang_url}/{schema_name}" data = self._get_raw(url=url, parse=False) - sys.stdout.write(f"Downloading YANG model {schema_name} ...\r\033[K") data = data.decode('utf-8') with open(f"{yangdir}/{schema_name}", 'w') as json_file: json_file.write(data) diff --git a/test/infamy/route.py b/test/infamy/route.py index 6e948f004..46b31a3ee 100644 --- a/test/infamy/route.py +++ b/test/infamy/route.py @@ -114,3 +114,13 @@ def ospf_is_area_nssa(target, area_id): return True return False + + +def ospf_has_neighbors(target): + ospf = _get_ospf_status(target) + for area in ospf.get("areas", {}).get("area", []): + for interface in area.get("interfaces", {}).get("interface", []): + if interface.get("neighbors"): + return True + + return False diff --git a/test/test.mk b/test/test.mk index 0d75fb182..5d9f687fd 100644 --- a/test/test.mk +++ b/test/test.mk @@ -1,9 +1,10 @@ base-dir := $(lastword $(subst :, ,$(BR2_EXTERNAL))) -test-dir := $(BR2_EXTERNAL_INFIX_PATH)/test +test-dir ?= $(BR2_EXTERNAL_INFIX_PATH)/test ninepm := $(BR2_EXTERNAL_INFIX_PATH)/test/9pm/9pm.py +NINEPM_PROJ_CONF ?= $(BR2_EXTERNAL_INFIX_PATH)/test/9pm-proj.yaml spec-dir := $(test-dir)/spec -test-specification := $(O)/images/test-specification.pdf - +test-specification := $(BINARIES_DIR)/test-specification.pdf +test-report := $(BINARIES_DIR)/test-report.pdf UNIT_TESTS ?= $(test-dir)/case/all-repo.yaml $(test-dir)/case/all-unit.yaml TESTS ?= $(test-dir)/case/all.yaml @@ -33,9 +34,18 @@ test-sh: test-spec: @esc_infix_name="$(echo $(INFIX_NAME) | sed 's/\//\\\//g')"; \ - sed 's/{REPLACE}/$(subst ",,$(esc_infix_name)) $(INFIX_VERSION)/' $(spec-dir)/Readme.adoc.in > $(spec-dir)/Readme.adoc + sed 's/{REPLACE}/$(subst ",,$(esc_infix_name)) $(INFIX_VERSION)/' \ + $(spec-dir)/Readme.adoc.in > $(spec-dir)/Readme.adoc @$(spec-dir)/generate_spec.py -s $(test-dir)/case/all.yaml -r $(BR2_EXTERNAL_INFIX_PATH) - @asciidoctor-pdf --failure-level INFO --theme $(spec-dir)/theme.yml -a pdf-fontsdir=$(spec-dir)/fonts -o $(test-specification) $(spec-dir)/Readme.adoc + @asciidoctor-pdf --failure-level INFO --theme $(spec-dir)/theme.yml \ + -a pdf-fontsdir=$(spec-dir)/fonts \ + -o $(test-specification) $(spec-dir)/Readme.adoc + +test-report: + asciidoctor-pdf --theme $(spec-dir)/theme.yml \ + -a logo="image:$(LOGO)" \ + -a pdf-fontsdir=$(spec-dir)/fonts \ + -o $(test-report) $(test-dir)/.log/last/report.adoc # Unit tests run with random (-r) hostname and container name to # prevent race conditions when running in CI environments.