From f6f84b10574057222a00e2ddf24f1f3f683d7d2b Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Tue, 20 Oct 2026 17:12:18 +0800 Subject: [PATCH 1/3] add initial borescope test --- tests/functional/test_borescope.py | 121 +++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 tests/functional/test_borescope.py diff --git a/tests/functional/test_borescope.py b/tests/functional/test_borescope.py new file mode 100644 index 0000000..2c58e30 --- /dev/null +++ b/tests/functional/test_borescope.py @@ -0,0 +1,121 @@ +import pathlib +import subprocess + +import helpers_functional as helpers + +PACKAGE_DIR = pathlib.Path(__file__).parent.parent.parent +BORESCOPE = [ + "uvx", + "borescope", + "--socket", + ".jjx/socket", +] + + +def test_borescope_probes_workload(k8s_2_configurable): + # Start the workload detached. + # The containers stay up after jjx exits, so borescope can probe them. + command = [ + "uvx", + "--with-editable", + PACKAGE_DIR, + "jjx", + "-d", + ] + result = subprocess.run( + command, + cwd=k8s_2_configurable, + capture_output=True, + text=True, + check=False, + ) + try: + assert result.returncode == 0, ( + f"jjx exited with code {result.returncode}\n" + f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + ) + assert "Workload running at " in result.stdout + model_name = result.stdout.split("--juju-model ")[1].split()[0] + container_name = f"{model_name}-test-charm-demo-server" + operator_container_name = f"{model_name}-test-charm-operator" + helpers.assert_container(container_name) + helpers.assert_container(operator_container_name) + # Probe the workload's services with borescope over the Pebble socket. + # This is the usage documented in the README. + command = [ + *BORESCOPE, + "--command", + "services", + ] + result = subprocess.run( + command, + cwd=k8s_2_configurable, + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, ( + f"borescope services exited with code {result.returncode}\n" + f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + ) + assert "fastapi" in result.stdout + assert "active" in result.stdout + # Check that borescope can see the workload process. + command = [ + *BORESCOPE, + "--command", + "ps", + ] + result = subprocess.run( + command, + cwd=k8s_2_configurable, + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, ( + f"borescope ps exited with code {result.returncode}\n" + f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + ) + assert "uvicorn" in result.stdout + # TEARDOWN + command = [ + "uvx", + "--with-editable", + PACKAGE_DIR, + "jjx", + "down", + ] + result = subprocess.run( + command, + cwd=k8s_2_configurable, + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, ( + f"jjx down exited with code {result.returncode}\n" + f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + ) + assert f"Removed {container_name}" in result.stdout + assert f"Removed {operator_container_name}" in result.stdout + helpers.assert_no_container(container_name) + helpers.assert_no_container(operator_container_name) + assert not (k8s_2_configurable / ".jjx").exists() + finally: + # Safety net for jjx -d: run jjx down in case an assertion + # failed before TEARDOWN completed. + command = [ + "uvx", + "--with-editable", + PACKAGE_DIR, + "jjx", + "down", + ] + subprocess.run( + command, + cwd=k8s_2_configurable, + capture_output=True, + text=True, + check=False, + ) From bf1443e330abbda00aa70025166d741e85bdaf33 Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Tue, 20 Oct 2026 19:35:42 +0800 Subject: [PATCH 2/3] fix: pin borescope to Python 3.11 in functional test CI's functional workflow sets UV_PYTHON=3.10, which uvx inherits, but borescope requires Python 3.11+. Pass --python 3.11 to uvx so the tool environment resolves regardless of UV_PYTHON. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/functional/test_borescope.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/functional/test_borescope.py b/tests/functional/test_borescope.py index 2c58e30..c10aefd 100644 --- a/tests/functional/test_borescope.py +++ b/tests/functional/test_borescope.py @@ -6,6 +6,9 @@ PACKAGE_DIR = pathlib.Path(__file__).parent.parent.parent BORESCOPE = [ "uvx", + # CI pins UV_PYTHON=3.10, but borescope requires Python 3.11+. + "--python", + "3.11", "borescope", "--socket", ".jjx/socket", From a2db72ae1e8d50b496d6d797a6c41a7a169385f8 Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Wed, 21 Oct 2026 09:03:32 +0800 Subject: [PATCH 3/3] switch to workflow --- .github/workflows/borescope.yaml | 47 +++++++++++ tests/functional/test_borescope.py | 124 ----------------------------- 2 files changed, 47 insertions(+), 124 deletions(-) create mode 100644 .github/workflows/borescope.yaml delete mode 100644 tests/functional/test_borescope.py diff --git a/.github/workflows/borescope.yaml b/.github/workflows/borescope.yaml new file mode 100644 index 0000000..3ee1941 --- /dev/null +++ b/.github/workflows/borescope.yaml @@ -0,0 +1,47 @@ +name: Borescope + +on: + pull_request: + push: + branches: + - main + +permissions: {} + +jobs: + borescope: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Check out Ops + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + repository: canonical/operator + ref: main + fetch-depth: 1 + path: operator + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + - name: Start the workload detached + working-directory: operator/examples/httpbin-demo + run: uvx --with-editable "$GITHUB_WORKSPACE" jjx -d + - name: Probe services with borescope + working-directory: operator/examples/httpbin-demo + run: | + services=$(uvx borescope --socket .jjx/socket --command services) + echo "$services" + echo "$services" | grep httpbin + echo "$services" | grep active + - name: Probe processes with borescope + working-directory: operator/examples/httpbin-demo + run: | + processes=$(uvx borescope --socket .jjx/socket --command ps) + echo "$processes" + echo "$processes" | grep gunicorn + - name: Tear down + if: always() + working-directory: operator/examples/httpbin-demo + run: uvx --with-editable "$GITHUB_WORKSPACE" jjx down diff --git a/tests/functional/test_borescope.py b/tests/functional/test_borescope.py deleted file mode 100644 index c10aefd..0000000 --- a/tests/functional/test_borescope.py +++ /dev/null @@ -1,124 +0,0 @@ -import pathlib -import subprocess - -import helpers_functional as helpers - -PACKAGE_DIR = pathlib.Path(__file__).parent.parent.parent -BORESCOPE = [ - "uvx", - # CI pins UV_PYTHON=3.10, but borescope requires Python 3.11+. - "--python", - "3.11", - "borescope", - "--socket", - ".jjx/socket", -] - - -def test_borescope_probes_workload(k8s_2_configurable): - # Start the workload detached. - # The containers stay up after jjx exits, so borescope can probe them. - command = [ - "uvx", - "--with-editable", - PACKAGE_DIR, - "jjx", - "-d", - ] - result = subprocess.run( - command, - cwd=k8s_2_configurable, - capture_output=True, - text=True, - check=False, - ) - try: - assert result.returncode == 0, ( - f"jjx exited with code {result.returncode}\n" - f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" - ) - assert "Workload running at " in result.stdout - model_name = result.stdout.split("--juju-model ")[1].split()[0] - container_name = f"{model_name}-test-charm-demo-server" - operator_container_name = f"{model_name}-test-charm-operator" - helpers.assert_container(container_name) - helpers.assert_container(operator_container_name) - # Probe the workload's services with borescope over the Pebble socket. - # This is the usage documented in the README. - command = [ - *BORESCOPE, - "--command", - "services", - ] - result = subprocess.run( - command, - cwd=k8s_2_configurable, - capture_output=True, - text=True, - check=False, - ) - assert result.returncode == 0, ( - f"borescope services exited with code {result.returncode}\n" - f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" - ) - assert "fastapi" in result.stdout - assert "active" in result.stdout - # Check that borescope can see the workload process. - command = [ - *BORESCOPE, - "--command", - "ps", - ] - result = subprocess.run( - command, - cwd=k8s_2_configurable, - capture_output=True, - text=True, - check=False, - ) - assert result.returncode == 0, ( - f"borescope ps exited with code {result.returncode}\n" - f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" - ) - assert "uvicorn" in result.stdout - # TEARDOWN - command = [ - "uvx", - "--with-editable", - PACKAGE_DIR, - "jjx", - "down", - ] - result = subprocess.run( - command, - cwd=k8s_2_configurable, - capture_output=True, - text=True, - check=False, - ) - assert result.returncode == 0, ( - f"jjx down exited with code {result.returncode}\n" - f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" - ) - assert f"Removed {container_name}" in result.stdout - assert f"Removed {operator_container_name}" in result.stdout - helpers.assert_no_container(container_name) - helpers.assert_no_container(operator_container_name) - assert not (k8s_2_configurable / ".jjx").exists() - finally: - # Safety net for jjx -d: run jjx down in case an assertion - # failed before TEARDOWN completed. - command = [ - "uvx", - "--with-editable", - PACKAGE_DIR, - "jjx", - "down", - ] - subprocess.run( - command, - cwd=k8s_2_configurable, - capture_output=True, - text=True, - check=False, - )