diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index dc47e46..96b09d5 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -136,23 +136,6 @@ steps: - "$$BUILDKITE_TEST_ENGINE_CLIENT_PATH:/usr/local/bin/bktec" propagate-environment: true - - label: ":pytest: :pants: pytest (Pants)" - # Pants owns test discovery and execution; the buildkite-test-collector - # pytest plugin uploads results to Test Engine. Authentication uses an - # ephemeral OIDC token minted in bin/test (no static suite token), which - # needs the agent binary inside the container (mount-buildkite-agent). - command: "cd pytest-pants && bin/test" - soft_fail: - - exit_status: 1 - plugins: - - docker#v5.13.0: - image: "python:3.13.2-bookworm" - # Pass pipeline env variables (incl. BUILDKITE_* run metadata) to the container - propagate-environment: true - # Make buildkite-agent available in the container for OIDC token requests - # See https://buildkite.com/docs/test-engine/test-collection/oidc - mount-buildkite-agent: true - - label: ":swift: Swift: XCTest (test-collector-swift)" command: "cd swift-xctest && bin/test" soft_fail: @@ -185,23 +168,6 @@ steps: environment: - BUILDKITE_ANALYTICS_TOKEN - - label: ":dotnet: NUnit" - parallelism: 2 - command: "cd nunit && bin/test" - soft_fail: - - exit_status: 1 - plugins: - - tests#v1.0.0: - test-runner: nunit - result-path: test-results/results.xml - test-file-pattern: "tests/**/*Tests.cs" - suite-slug: test-engine-client-examples - tags: - - "language.name=dotnet" - - "test.framework.name=nunit" - - mise#v1.1.1: - dir: nunit - - label: ":golang: Go test" parallelism: 2 command: "cd go && bin/test" diff --git a/.gitignore b/.gitignore index 5cf5cf1..24520cf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ node_modules/ tmp/ bktec -# nunit / .NET -test-results/ - # pytest *.egg* __pycache__ diff --git a/nunit/.gitignore b/nunit/.gitignore deleted file mode 100644 index a5ae045..0000000 --- a/nunit/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -test-results/ -src/**/bin/ -src/**/obj/ -tests/**/bin/ -tests/**/obj/ -*.user diff --git a/nunit/NUnitSpike.slnx b/nunit/NUnitSpike.slnx deleted file mode 100644 index 2276c48..0000000 --- a/nunit/NUnitSpike.slnx +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/nunit/README.md b/nunit/README.md deleted file mode 100644 index 00b29c7..0000000 --- a/nunit/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# NUnit - -A .NET NUnit example demonstrating [Buildkite Test Engine](https://buildkite.com/docs/test-engine) test splitting using `bktec` with the built-in NUnit runner. - -## Structure - -``` -bin/test # Entry point: builds, then runs bktec configured by the tests plugin -src/MyLib/ # Library under test -tests/MyLib.Tests/ # NUnit test project (25 tests across 3 files) -mise.toml # .NET SDK managed by mise -NUnitSpike.slnx # Solution file -``` - -## How it works - -1. The Buildkite pipeline's `tests#v1.0.0` plugin installs `bktec`, authenticates with OIDC, and configures: - - `BUILDKITE_TEST_ENGINE_TEST_RUNNER=nunit` - - `BUILDKITE_TEST_ENGINE_TEST_FILE_PATTERN=tests/**/*Tests.cs` - - `BUILDKITE_TEST_ENGINE_RESULT_PATH=test-results/results.xml` - -2. `bin/test` builds the solution once with `dotnet build`, then runs `bktec run`. - -3. `bktec` discovers test files matching the pattern, requests a split plan from the Test Engine API, and runs `dotnet test --no-build --filter {{testFilter}} --logger junit;LogFilePath={{resultPath}}` for the files assigned to this node. - -## Pipeline - -The Buildkite pipeline step uses `parallelism: 2`, the [Tests plugin](https://github.com/buildkite-plugins/tests-buildkite-plugin) to install/configure `bktec`, and the [mise plugin](https://github.com/buildkite-plugins/mise-buildkite-plugin) (instead of Docker) to install the .NET SDK. `bktec` uploads JUnit XML results to Test Engine directly. - -## Local development - -### Prerequisites - -[mise](https://mise.jdx.dev/) manages the .NET SDK: - -```sh -mise install -``` - -### Run tests - -```sh -dotnet test -``` - -### Run tests with JUnit XML output - -```sh -dotnet test --logger "junit;LogFilePath=test-results/results.xml" -``` diff --git a/nunit/bin/test b/nunit/bin/test deleted file mode 100755 index acce29b..0000000 --- a/nunit/bin/test +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Build the solution first so bktec can use --no-build -dotnet build - -# Run Buildkite Test Engine Client configured by the tests Buildkite plugin -bktec run diff --git a/nunit/mise.toml b/nunit/mise.toml deleted file mode 100644 index 146768f..0000000 --- a/nunit/mise.toml +++ /dev/null @@ -1,3 +0,0 @@ -[tools] -dotnet = "latest" -go = "latest" diff --git a/nunit/src/MyLib/Calculator.cs b/nunit/src/MyLib/Calculator.cs deleted file mode 100644 index 769db4f..0000000 --- a/nunit/src/MyLib/Calculator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MyLib; - -public class Calculator -{ - public int Add(int a, int b) => a + b; - public int Subtract(int a, int b) => a - b; - public int Multiply(int a, int b) => a * b; - public double Divide(int a, int b) => - b == 0 ? throw new DivideByZeroException() : (double)a / b; -} diff --git a/nunit/src/MyLib/Collections.cs b/nunit/src/MyLib/Collections.cs deleted file mode 100644 index 3a8c576..0000000 --- a/nunit/src/MyLib/Collections.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace MyLib; - -public class SimpleStack -{ - private readonly List _items = []; - - public int Count => _items.Count; - public bool IsEmpty => _items.Count == 0; - - public void Push(T item) => _items.Add(item); - - public T Pop() - { - if (IsEmpty) throw new InvalidOperationException("Stack is empty"); - var item = _items[^1]; - _items.RemoveAt(_items.Count - 1); - return item; - } - - public T Peek() - { - if (IsEmpty) throw new InvalidOperationException("Stack is empty"); - return _items[^1]; - } -} diff --git a/nunit/src/MyLib/MyLib.csproj b/nunit/src/MyLib/MyLib.csproj deleted file mode 100644 index b760144..0000000 --- a/nunit/src/MyLib/MyLib.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net10.0 - enable - enable - - - diff --git a/nunit/src/MyLib/StringUtils.cs b/nunit/src/MyLib/StringUtils.cs deleted file mode 100644 index b4fdf36..0000000 --- a/nunit/src/MyLib/StringUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MyLib; - -public static class StringUtils -{ - public static string Reverse(string input) => - new(input.Reverse().ToArray()); - - public static bool IsPalindrome(string input) => - input.Equals(Reverse(input), StringComparison.OrdinalIgnoreCase); - - public static int WordCount(string input) => - string.IsNullOrWhiteSpace(input) ? 0 : input.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length; -} diff --git a/nunit/tests/MyLib.Tests/CalculatorTests.cs b/nunit/tests/MyLib.Tests/CalculatorTests.cs deleted file mode 100644 index 330f076..0000000 --- a/nunit/tests/MyLib.Tests/CalculatorTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -using MyLib; - -namespace MyLib.Tests; - -public class CalculatorTests -{ - private Calculator _calc = null!; - - [SetUp] - public void SetUp() => _calc = new Calculator(); - - [Test] - public void Add_ReturnsSumOfTwoNumbers() => - Assert.That(_calc.Add(2, 3), Is.EqualTo(5)); - - [Test] - public void Add_HandlesNegativeNumbers() => - Assert.That(_calc.Add(-1, -2), Is.EqualTo(-3)); - - [Test] - public void Subtract_ReturnsDifference() => - Assert.That(_calc.Subtract(10, 4), Is.EqualTo(6)); - - [Test] - public void Multiply_ReturnsProduct() => - Assert.That(_calc.Multiply(3, 7), Is.EqualTo(21)); - - [Test] - public void Divide_ReturnsQuotient() => - Assert.That(_calc.Divide(10, 4), Is.EqualTo(2.5)); - - [Test] - public void Divide_ByZero_Throws() => - Assert.That(() => _calc.Divide(1, 0), Throws.TypeOf()); - - [TestCase(0, 0, 0)] - [TestCase(1, 1, 2)] - [TestCase(100, -50, 50)] - public void Add_Parameterized(int a, int b, int expected) => - Assert.That(_calc.Add(a, b), Is.EqualTo(expected)); -} diff --git a/nunit/tests/MyLib.Tests/MyLib.Tests.csproj b/nunit/tests/MyLib.Tests/MyLib.Tests.csproj deleted file mode 100644 index 6b1e3a8..0000000 --- a/nunit/tests/MyLib.Tests/MyLib.Tests.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net10.0 - latest - enable - enable - false - - - - - - - - - - - - - - - - - - - - diff --git a/nunit/tests/MyLib.Tests/SimpleStackTests.cs b/nunit/tests/MyLib.Tests/SimpleStackTests.cs deleted file mode 100644 index 69cb07c..0000000 --- a/nunit/tests/MyLib.Tests/SimpleStackTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MyLib; - -namespace MyLib.Tests; - -public class SimpleStackTests -{ - private SimpleStack _stack = null!; - - [SetUp] - public void SetUp() => _stack = new SimpleStack(); - - [Test] - public void NewStack_IsEmpty() => - Assert.That(_stack.IsEmpty, Is.True); - - [Test] - public void Push_IncreasesCount() - { - _stack.Push(1); - Assert.That(_stack.Count, Is.EqualTo(1)); - } - - [Test] - public void Pop_ReturnsLastPushedItem() - { - _stack.Push(1); - _stack.Push(2); - Assert.That(_stack.Pop(), Is.EqualTo(2)); - } - - [Test] - public void Pop_DecreasesCount() - { - _stack.Push(1); - _stack.Pop(); - Assert.That(_stack.Count, Is.EqualTo(0)); - } - - [Test] - public void Pop_WhenEmpty_Throws() => - Assert.That(() => _stack.Pop(), Throws.TypeOf()); - - [Test] - public void Peek_ReturnsTopWithoutRemoving() - { - _stack.Push(42); - Assert.That(_stack.Peek(), Is.EqualTo(42)); - Assert.That(_stack.Count, Is.EqualTo(1)); - } - - [Test] - public void Peek_WhenEmpty_Throws() => - Assert.That(() => _stack.Peek(), Throws.TypeOf()); -} diff --git a/nunit/tests/MyLib.Tests/StringUtilsTests.cs b/nunit/tests/MyLib.Tests/StringUtilsTests.cs deleted file mode 100644 index f298fe2..0000000 --- a/nunit/tests/MyLib.Tests/StringUtilsTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using MyLib; - -namespace MyLib.Tests; - -public class StringUtilsTests -{ - [Test] - public void Reverse_ReversesString() => - Assert.That(StringUtils.Reverse("hello"), Is.EqualTo("olleh")); - - [Test] - public void Reverse_EmptyString_ReturnsEmpty() => - Assert.That(StringUtils.Reverse(""), Is.EqualTo("")); - - [Test] - public void IsPalindrome_True_ForPalindrome() => - Assert.That(StringUtils.IsPalindrome("racecar"), Is.True); - - [Test] - public void IsPalindrome_True_CaseInsensitive() => - Assert.That(StringUtils.IsPalindrome("Madam"), Is.True); - - [Test] - public void IsPalindrome_False_ForNonPalindrome() => - Assert.That(StringUtils.IsPalindrome("hello"), Is.False); - - [TestCase("hello world", 2)] - [TestCase("one", 1)] - [TestCase(" spaced out ", 2)] - [TestCase("", 0)] - public void WordCount_ReturnsCorrectCount(string input, int expected) => - Assert.That(StringUtils.WordCount(input), Is.EqualTo(expected)); -} diff --git a/pytest-pants/3rdparty/python/BUILD b/pytest-pants/3rdparty/python/BUILD deleted file mode 100644 index 3c6a639..0000000 --- a/pytest-pants/3rdparty/python/BUILD +++ /dev/null @@ -1,8 +0,0 @@ -python_requirements( - name="pytest", - source="pytest-requirements.txt", - resolve="pytest", - overrides={ - "pytest": {"entry_point": "pytest:console_main"}, - }, -) diff --git a/pytest-pants/3rdparty/python/pytest-requirements.txt b/pytest-pants/3rdparty/python/pytest-requirements.txt deleted file mode 100644 index fd50fb9..0000000 --- a/pytest-pants/3rdparty/python/pytest-requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -buildkite-test-collector>=1.0.4,<2.0.0 -pytest>=8.0.0,<9.0.0 diff --git a/pytest-pants/3rdparty/python/pytest.lock b/pytest-pants/3rdparty/python/pytest.lock deleted file mode 100644 index 52419f2..0000000 --- a/pytest-pants/3rdparty/python/pytest.lock +++ /dev/null @@ -1,396 +0,0 @@ -// This lockfile was autogenerated by Pants. To regenerate, run: -// -// pants generate-lockfiles --resolve=pytest -// -// --- BEGIN PANTS LOCKFILE METADATA: DO NOT EDIT OR REMOVE --- -// { -// "version": 6, -// "valid_for_interpreter_constraints": [ -// "CPython==3.13.*" -// ], -// "generated_with_requirements": [ -// "buildkite-test-collector<2.0.0,>=1.0.4", -// "pytest<9.0.0,>=8.0.0" -// ], -// "manylinux": "manylinux2014", -// "requirement_constraints": [], -// "only_binary": [], -// "no_binary": [], -// "excludes": [], -// "overrides": [], -// "sources": [], -// "lock_style": "universal", -// "complete_platforms": [] -// } -// --- END PANTS LOCKFILE METADATA --- - -{ - "allow_builds": true, - "allow_prereleases": false, - "allow_wheels": true, - "build_isolation": true, - "constraints": [], - "elide_unused_requires_dist": false, - "excluded": [], - "locked_resolves": [ - { - "locked_requirements": [ - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "00e457506fffea131a6f756ec35114bf825f8dec2b4ca3a8fc1f2558b99416ab", - "url": "https://files.pythonhosted.org/packages/c9/c2/4712b95543c5c59e49e27a8699bc60d6b28b23f9bedcd799b264c599a444/buildkite_test_collector-1.4.3-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "ea468c13a535767449ddd14649b598a930daac8df359d76b2c15951a6503e232", - "url": "https://files.pythonhosted.org/packages/57/bb/edcfe5494907e19f16bfbea281346553a0622f73ca61c3d8c87e8db9cfb6/buildkite_test_collector-1.4.3.tar.gz" - } - ], - "project_name": "buildkite-test-collector", - "requires_dists": [ - "check-manifest; extra == \"dev\"", - "filelock>=3", - "mock>=4; extra == \"dev\"", - "pylint; extra == \"dev\"", - "pytest>=7", - "requests>=2", - "responses; extra == \"dev\"", - "twine; extra == \"dev\"" - ], - "requires_python": ">=3.9", - "version": "1.4.3" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", - "url": "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", - "url": "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz" - } - ], - "project_name": "certifi", - "requires_dists": [], - "requires_python": ">=3.7", - "version": "2026.5.20" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", - "url": "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", - "url": "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl" - }, - { - "algorithm": "sha256", - "hash": "64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", - "url": "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", - "url": "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl" - }, - { - "algorithm": "sha256", - "hash": "3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", - "url": "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl" - }, - { - "algorithm": "sha256", - "hash": "3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", - "url": "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl" - }, - { - "algorithm": "sha256", - "hash": "481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", - "url": "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl" - }, - { - "algorithm": "sha256", - "hash": "f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", - "url": "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl" - }, - { - "algorithm": "sha256", - "hash": "f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", - "url": "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl" - }, - { - "algorithm": "sha256", - "hash": "a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", - "url": "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl" - }, - { - "algorithm": "sha256", - "hash": "e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", - "url": "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl" - }, - { - "algorithm": "sha256", - "hash": "f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", - "url": "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl" - }, - { - "algorithm": "sha256", - "hash": "f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", - "url": "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl" - }, - { - "algorithm": "sha256", - "hash": "ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", - "url": "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz" - }, - { - "algorithm": "sha256", - "hash": "e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", - "url": "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl" - } - ], - "project_name": "charset-normalizer", - "requires_dists": [], - "requires_python": ">=3.7", - "version": "3.4.7" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", - "url": "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", - "url": "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz" - } - ], - "project_name": "filelock", - "requires_dists": [], - "requires_python": ">=3.10", - "version": "3.29.1" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", - "url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", - "url": "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz" - } - ], - "project_name": "idna", - "requires_dists": [ - "mypy>=1.11.2; extra == \"all\"", - "pytest>=8.3.2; extra == \"all\"", - "ruff>=0.6.2; extra == \"all\"" - ], - "requires_python": ">=3.9", - "version": "3.18" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", - "url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", - "url": "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz" - } - ], - "project_name": "iniconfig", - "requires_dists": [], - "requires_python": ">=3.10", - "version": "2.3.0" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", - "url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", - "url": "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz" - } - ], - "project_name": "packaging", - "requires_dists": [], - "requires_python": ">=3.8", - "version": "26.2" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", - "url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", - "url": "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz" - } - ], - "project_name": "pluggy", - "requires_dists": [ - "coverage; extra == \"testing\"", - "pre-commit; extra == \"dev\"", - "pytest-benchmark; extra == \"testing\"", - "pytest; extra == \"testing\"", - "tox; extra == \"dev\"" - ], - "requires_python": ">=3.9", - "version": "1.6.0" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", - "url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", - "url": "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz" - } - ], - "project_name": "pygments", - "requires_dists": [ - "colorama>=0.4.6; extra == \"windows-terminal\"" - ], - "requires_python": ">=3.9", - "version": "2.20.0" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", - "url": "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", - "url": "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz" - } - ], - "project_name": "pytest", - "requires_dists": [ - "argcomplete; extra == \"dev\"", - "attrs>=19.2; extra == \"dev\"", - "colorama>=0.4; sys_platform == \"win32\"", - "exceptiongroup>=1; python_version < \"3.11\"", - "hypothesis>=3.56; extra == \"dev\"", - "iniconfig>=1", - "mock; extra == \"dev\"", - "packaging>=20", - "pluggy<2,>=1.5", - "pygments>=2.7.2", - "requests; extra == \"dev\"", - "setuptools; extra == \"dev\"", - "tomli>=1; python_version < \"3.11\"", - "xmlschema; extra == \"dev\"" - ], - "requires_python": ">=3.9", - "version": "8.4.2" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", - "url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", - "url": "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz" - } - ], - "project_name": "requests", - "requires_dists": [ - "PySocks!=1.5.7,>=1.5.6; extra == \"socks\"", - "certifi>=2023.5.7", - "chardet<8,>=3.0.2; extra == \"use-chardet-on-py3\"", - "charset_normalizer<4,>=2", - "idna<4,>=2.5", - "urllib3<3,>=1.26" - ], - "requires_python": ">=3.10", - "version": "2.34.2" - }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", - "url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", - "url": "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz" - } - ], - "project_name": "urllib3", - "requires_dists": [ - "backports-zstd>=1.0.0; python_version < \"3.14\" and extra == \"zstd\"", - "brotli>=1.2.0; platform_python_implementation == \"CPython\" and extra == \"brotli\"", - "brotlicffi>=1.2.0.0; platform_python_implementation != \"CPython\" and extra == \"brotli\"", - "h2<5,>=4; extra == \"h2\"", - "pysocks!=1.5.7,<2.0,>=1.5.6; extra == \"socks\"" - ], - "requires_python": ">=3.10", - "version": "2.7.0" - } - ], - "marker": null, - "platform_tag": null - } - ], - "only_builds": [], - "only_wheels": [], - "overridden": [], - "path_mappings": {}, - "pex_version": "2.81.0", - "pip_version": "24.2", - "prefer_older_binary": false, - "requirements": [ - "buildkite-test-collector<2.0.0,>=1.0.4", - "pytest<9.0.0,>=8.0.0" - ], - "requires_python": [ - "CPython==3.13.*" - ], - "resolver_version": "pip-2020-resolver", - "style": "universal", - "target_systems": [ - "linux", - "mac" - ], - "transitive": true, - "use_pep517": null, - "use_system_time": false -} diff --git a/pytest-pants/README.md b/pytest-pants/README.md deleted file mode 100644 index d8a977b..0000000 --- a/pytest-pants/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# pytest + Pants - -An example of running a [Pants](https://www.pantsbuild.org/) Python codebase and -reporting results to Buildkite Test Engine. - -Pants owns test discovery and execution (`pants test ::`), and the -[`buildkite-test-collector`](https://pypi.org/project/buildkite-test-collector/) -pytest plugin uploads results to Test Engine. Pants runs each module's tests as -its own pytest process. - -> [!NOTE] -> The Test Engine Client (`bktec`) has an experimental `pytest-pants` runner, but -> it is lightly used and requires job parallelism to be configured. This example -> instead drives Pants directly and uploads via the collector, which keeps the -> mandatory "tests run in CI" path simple while still reporting to Test Engine. - -## Authentication (OIDC) - -Result uploads authenticate with an ephemeral [OIDC token](https://buildkite.com/docs/test-engine/test-collection/oidc) -rather than a long-lived suite token. `bin/test` mints one with -`buildkite-agent oidc request-token` and exports it as `BUILDKITE_ANALYTICS_TOKEN`, -which the collector reads. This requires: - -- an OIDC policy on the suite permitting this pipeline, and -- `mount-buildkite-agent: true` on the docker plugin (so `buildkite-agent` is - available inside the container). - -## Execution tags - -[`src/conftest.py`](src/conftest.py) tags every test execution via -`pytest.mark.execution_tag(...)` so these uploads are identifiable in the shared -suite: `test.framework.name=pytest`, `test.framework.version=`, and -`build.tool=pants`. Pants picks up `conftest.py` automatically through the -`python_test_utils()` target in [`src/BUILD`](src/BUILD). - -## Project layout - -The codebase is split into several small Pants targets that exercise different -dependency relationships. Pants infers these edges automatically from the -`import` statements (no manual dependency lists in the `BUILD` files): - -```diagram - textutils (independent — no internal dependencies) - - calc ◀───── geometry ◀───── report - (leaf) (1st level) (2nd level, transitively needs calc) -``` - -| Module | Depends on | Role | -| ---------------------- | --------------------- | ---------------------------- | -| `src/calc` | — | Leaf / non-dependent target | -| `src/geometry` | `calc` | First-level dependency | -| `src/report` | `geometry` (→ `calc`) | Second-level (transitive) | -| `src/textutils` | — | Independent target | - -Each module has a `BUILD` file declaring `python_sources()` and -`python_tests()`, source plus `*_test.py` tests, and is run by Pants as its own -pytest process. - -## Running locally - -Install Pants (once): https://www.pantsbuild.org/stable/docs/getting-started/installing-pants - -```sh -# Run every test target -pants test :: - -# Inspect the inferred dependency graph -pants dependencies --transitive src/report/summary.py - -# Regenerate the lockfile after changing 3rdparty/python/pytest-requirements.txt -pants generate-lockfiles -``` - -## Running in CI - -`bin/test` installs Pants, mints an OIDC token for the suite, and runs -`pants test ::`. See [`bin/test`](bin/test) and the pipeline step in -[`.buildkite/pipeline.yml`](../.buildkite/pipeline.yml). diff --git a/pytest-pants/bin/test b/pytest-pants/bin/test deleted file mode 100755 index 067c52a..0000000 --- a/pytest-pants/bin/test +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Install Pants, the build tool that owns test discovery and execution for this -# project. See https://www.pantsbuild.org/stable/docs/getting-started/installing-pants -curl --proto '=https' --tlsv1.2 -fsSL --retry 5 --retry-all-errors --retry-delay 2 \ - https://static.pantsbuild.org/setup/get-pants.sh | bash -export PATH="${HOME}/.local/bin:${PATH}" - -# Request an ephemeral OIDC token to authenticate result uploads to the Test -# Engine suite, instead of a long-lived suite token. The buildkite-test-collector -# pytest plugin reads BUILDKITE_ANALYTICS_TOKEN (passed into the pytest sandbox by -# Pants via [test].extra_env_vars in pants.toml). -# See https://buildkite.com/docs/test-engine/test-collection/oidc -# -# The suite must have an OIDC policy permitting this pipeline. Requires the agent -# binary in the container (docker plugin: mount-buildkite-agent: true). The token -# is registered with the agent's log redactor over the Job API socket, which the -# docker plugin mounts into the container (v5.12.0+). -if command -v buildkite-agent >/dev/null 2>&1; then - SUITE_URL="https://buildkite.com/organizations/buildkite/analytics/suites/test-engine-client-examples" - BUILDKITE_ANALYTICS_TOKEN="$(buildkite-agent oidc request-token --audience "${SUITE_URL}" --lifetime 1800)" - export BUILDKITE_ANALYTICS_TOKEN -else - echo "buildkite-agent not found; skipping Test Engine OIDC token (results won't be uploaded)" -fi - -# Run every test target. Pants runs each module's tests as its own pytest -# process; the collector uploads results to Test Engine when the token is set. -pants test :: diff --git a/pytest-pants/pants.toml b/pytest-pants/pants.toml deleted file mode 100644 index f9e9ed6..0000000 --- a/pytest-pants/pants.toml +++ /dev/null @@ -1,49 +0,0 @@ -[GLOBAL] -pants_version = "2.31.0" -backend_packages = [ - "pants.backend.python", - # Let Pants fetch its own hermetic CPython interpreter (Python Build - # Standalone) so the example does not depend on the interpreter that happens - # to be installed on the machine or in the CI image. - "pants.backend.python.providers.experimental.python_build_standalone", -] - -[python] -interpreter_constraints = ["CPython==3.13.*"] -enable_resolves = true -resolves_generate_lockfiles = true -# Everything (first-party code, tests, and the pytest tool) uses the one resolve -# so the buildkite-test-collector pytest plugin is available when running tests. -default_resolve = "pytest" - -[python.resolves] -pytest = "3rdparty/python/pytest.lock" - -[pytest] -# Install pytest (and its plugins, e.g. buildkite-test-collector) from the -# resolve above rather than from Pants' built-in default lockfile. -install_from_resolve = "pytest" -requirements = ["//3rdparty/python:pytest"] - -[test] -# Expose the Buildkite environment to the pytest sandbox so the -# buildkite-test-collector plugin can attach CI metadata and upload results to -# Test Engine. Unset variables (e.g. when running locally) are simply ignored, -# and without BUILDKITE_ANALYTICS_TOKEN the collector just writes results -# locally instead of uploading. -extra_env_vars = [ - "CI", - "BUILDKITE_ANALYTICS_TOKEN", - "BUILDKITE_BUILD_ID", - "BUILDKITE_BUILD_NUMBER", - "BUILDKITE_BUILD_URL", - "BUILDKITE_JOB_ID", - "BUILDKITE_BRANCH", - "BUILDKITE_COMMIT", - "BUILDKITE_MESSAGE", - "BUILDKITE_ORGANIZATION_SLUG", - "BUILDKITE_PIPELINE_SLUG", -] - -[source] -root_patterns = ["src"] diff --git a/pytest-pants/src/BUILD b/pytest-pants/src/BUILD deleted file mode 100644 index 7e09b12..0000000 --- a/pytest-pants/src/BUILD +++ /dev/null @@ -1,3 +0,0 @@ -# conftest.py is shared test infrastructure; python_test_utils() makes Pants -# treat it as a dependency of every test under src/. -python_test_utils() diff --git a/pytest-pants/src/calc/BUILD b/pytest-pants/src/calc/BUILD deleted file mode 100644 index 4991ead..0000000 --- a/pytest-pants/src/calc/BUILD +++ /dev/null @@ -1,3 +0,0 @@ -python_sources() - -python_tests(name="tests") diff --git a/pytest-pants/src/calc/__init__.py b/pytest-pants/src/calc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pytest-pants/src/calc/arithmetic.py b/pytest-pants/src/calc/arithmetic.py deleted file mode 100644 index 065535e..0000000 --- a/pytest-pants/src/calc/arithmetic.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Core arithmetic helpers. - -This is a leaf module: it has no dependencies on any other module in the -codebase, which makes it a good illustration of a non-dependent Pants target. -""" - - -def add(a: float, b: float) -> float: - return a + b - - -def subtract(a: float, b: float) -> float: - return a - b - - -def multiply(a: float, b: float) -> float: - return a * b diff --git a/pytest-pants/src/calc/arithmetic_test.py b/pytest-pants/src/calc/arithmetic_test.py deleted file mode 100644 index ae99114..0000000 --- a/pytest-pants/src/calc/arithmetic_test.py +++ /dev/null @@ -1,13 +0,0 @@ -from calc.arithmetic import add, multiply, subtract - - -def test_add(): - assert add(1, 1) == 2 - - -def test_subtract(): - assert subtract(5, 3) == 2 - - -def test_multiply(): - assert multiply(4, 3) == 12 diff --git a/pytest-pants/src/conftest.py b/pytest-pants/src/conftest.py deleted file mode 100644 index 67f5e0e..0000000 --- a/pytest-pants/src/conftest.py +++ /dev/null @@ -1,9 +0,0 @@ -import pytest - - -def pytest_itemcollected(item): - # Tag every execution so these uploads are identifiable in Test Engine - # (all the examples in this repo report to the same suite). - item.add_marker(pytest.mark.execution_tag("test.framework.name", "pytest")) - item.add_marker(pytest.mark.execution_tag("test.framework.version", pytest.__version__)) - item.add_marker(pytest.mark.execution_tag("build.tool", "pants")) diff --git a/pytest-pants/src/geometry/BUILD b/pytest-pants/src/geometry/BUILD deleted file mode 100644 index 4991ead..0000000 --- a/pytest-pants/src/geometry/BUILD +++ /dev/null @@ -1,3 +0,0 @@ -python_sources() - -python_tests(name="tests") diff --git a/pytest-pants/src/geometry/__init__.py b/pytest-pants/src/geometry/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pytest-pants/src/geometry/area.py b/pytest-pants/src/geometry/area.py deleted file mode 100644 index 989b293..0000000 --- a/pytest-pants/src/geometry/area.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Geometry helpers. - -First-level dependency: this module imports ``calc.arithmetic``. Pants infers -the dependency on the ``calc`` target automatically from the import below. -""" - -from calc.arithmetic import multiply - - -def rectangle_area(width: float, height: float) -> float: - return multiply(width, height) - - -def square_area(side: float) -> float: - return rectangle_area(side, side) diff --git a/pytest-pants/src/geometry/area_test.py b/pytest-pants/src/geometry/area_test.py deleted file mode 100644 index 2ad0f4d..0000000 --- a/pytest-pants/src/geometry/area_test.py +++ /dev/null @@ -1,9 +0,0 @@ -from geometry.area import rectangle_area, square_area - - -def test_rectangle_area(): - assert rectangle_area(4, 3) == 12 - - -def test_square_area(): - assert square_area(5) == 25 diff --git a/pytest-pants/src/report/BUILD b/pytest-pants/src/report/BUILD deleted file mode 100644 index 4991ead..0000000 --- a/pytest-pants/src/report/BUILD +++ /dev/null @@ -1,3 +0,0 @@ -python_sources() - -python_tests(name="tests") diff --git a/pytest-pants/src/report/__init__.py b/pytest-pants/src/report/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pytest-pants/src/report/summary.py b/pytest-pants/src/report/summary.py deleted file mode 100644 index eb71fa8..0000000 --- a/pytest-pants/src/report/summary.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Reporting helpers. - -Second-level dependency: this module imports ``geometry.area``, which in turn -depends on ``calc.arithmetic``. Pants infers the full transitive dependency -graph (report -> geometry -> calc) from the imports. -""" - -from geometry.area import rectangle_area, square_area - - -def area_summary(rectangles: list[tuple[float, float]], squares: list[float]) -> str: - total = 0.0 - for width, height in rectangles: - total += rectangle_area(width, height) - for side in squares: - total += square_area(side) - count = len(rectangles) + len(squares) - return f"Total area across {count} shapes: {total:g}" diff --git a/pytest-pants/src/report/summary_test.py b/pytest-pants/src/report/summary_test.py deleted file mode 100644 index 93539f1..0000000 --- a/pytest-pants/src/report/summary_test.py +++ /dev/null @@ -1,10 +0,0 @@ -from report.summary import area_summary - - -def test_area_summary(): - summary = area_summary(rectangles=[(4, 3), (2, 5)], squares=[3]) - assert summary == "Total area across 3 shapes: 31" - - -def test_area_summary_empty(): - assert area_summary(rectangles=[], squares=[]) == "Total area across 0 shapes: 0" diff --git a/pytest-pants/src/textutils/BUILD b/pytest-pants/src/textutils/BUILD deleted file mode 100644 index 4991ead..0000000 --- a/pytest-pants/src/textutils/BUILD +++ /dev/null @@ -1,3 +0,0 @@ -python_sources() - -python_tests(name="tests") diff --git a/pytest-pants/src/textutils/__init__.py b/pytest-pants/src/textutils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pytest-pants/src/textutils/slug.py b/pytest-pants/src/textutils/slug.py deleted file mode 100644 index ec3a835..0000000 --- a/pytest-pants/src/textutils/slug.py +++ /dev/null @@ -1,13 +0,0 @@ -"""String helpers. - -Another non-dependent module: it shares no code with ``calc``, ``geometry`` or -``report``, demonstrating an independent target in the dependency graph. -""" - - -def slugify(text: str) -> str: - return "-".join(text.lower().split()) - - -def titlecase(text: str) -> str: - return " ".join(word.capitalize() for word in text.split()) diff --git a/pytest-pants/src/textutils/slug_test.py b/pytest-pants/src/textutils/slug_test.py deleted file mode 100644 index 83dbdc2..0000000 --- a/pytest-pants/src/textutils/slug_test.py +++ /dev/null @@ -1,9 +0,0 @@ -from textutils.slug import slugify, titlecase - - -def test_slugify(): - assert slugify("Hello World Example") == "hello-world-example" - - -def test_titlecase(): - assert titlecase("the quick brown fox") == "The Quick Brown Fox"