Skip to content

Commit

Permalink
Deduplicate test fixtures (#1137)
Browse files Browse the repository at this point in the history
Co-authored-by: William Woodruff <william@trailofbits.com>
  • Loading branch information
facutuesca and woodruffw authored Sep 24, 2024
1 parent 29905fe commit 47e8359
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 222 deletions.
114 changes: 114 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Copyright 2024 The Sigstore Authors
#
# 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.

import os
from pathlib import Path

import pytest
from id import (
AmbientCredentialError,
GitHubOidcPermissionCredentialError,
detect_credential,
)

from sigstore.oidc import _DEFAULT_AUDIENCE

_ASSETS = (Path(__file__).parent / "assets").resolve()
assert _ASSETS.is_dir()


@pytest.fixture
def asset():
def _asset(name: str) -> Path:
return _ASSETS / name

return _asset


def _has_oidc_id():
# If there are tokens manually defined for us in the environment, use them.
if os.getenv("SIGSTORE_IDENTITY_TOKEN_production") or os.getenv(
"SIGSTORE_IDENTITY_TOKEN_staging"
):
return True

try:
token = detect_credential(_DEFAULT_AUDIENCE)
if token is None:
return False
except GitHubOidcPermissionCredentialError:
# On GitHub Actions, forks do not have access to OIDC identities.
# We differentiate this case from other GitHub credential errors,
# since it's a case where we want to skip (i.e. return False).
if os.getenv("GITHUB_EVENT_NAME") == "pull_request":
return False
return True
except AmbientCredentialError:
# If ambient credential detection raises, then we *are* in an ambient
# environment but one that's been configured incorrectly. We
# pass this through, so that the CI fails appropriately rather than
# silently skipping the faulty tests.
return True

return True


def pytest_addoption(parser):
parser.addoption(
"--skip-online",
action="store_true",
help="skip tests that require network connectivity",
)
parser.addoption(
"--skip-staging",
action="store_true",
help="skip tests that require Sigstore staging infrastructure",
)


def pytest_runtest_setup(item):
# Do we need a network connection?
online = False
for mark in ["online", "staging", "production"]:
if mark in item.keywords:
online = True

if online and item.config.getoption("--skip-online"):
pytest.skip(
"skipping test that requires network connectivity due to `--skip-online` flag"
)
elif "ambient_oidc" in item.keywords and not _has_oidc_id():
pytest.skip("skipping test that requires an ambient OIDC credential")

if "staging" in item.keywords and item.config.getoption("--skip-staging"):
pytest.skip(
"skipping test that requires staging infrastructure due to `--skip-staging` flag"
)


def pytest_configure(config):
config.addinivalue_line(
"markers", "staging: mark test as requiring Sigstore staging infrastructure"
)
config.addinivalue_line(
"markers",
"production: mark test as requiring Sigstore production infrastructure",
)
config.addinivalue_line(
"markers",
"online: mark test as requiring network connectivity (but not a specific Sigstore infrastructure)",
)
config.addinivalue_line(
"markers", "ambient_oidc: mark test as requiring an ambient OIDC identity"
)
80 changes: 2 additions & 78 deletions test/integration/cli/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,94 +11,18 @@
# 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.

import os
from pathlib import Path
from typing import Callable

import pytest
from id import (
AmbientCredentialError,
GitHubOidcPermissionCredentialError,
detect_credential,
)

from sigstore._cli import main
from sigstore.oidc import _DEFAULT_AUDIENCE

_ASSETS = (Path(__file__).parent.parent.parent / "assets/integration").resolve()
assert _ASSETS.is_dir()


def _has_oidc_id():
# If there are tokens manually defined for us in the environment, use them.
if os.getenv("SIGSTORE_IDENTITY_TOKEN_production") or os.getenv(
"SIGSTORE_IDENTITY_TOKEN_staging"
):
return True

try:
token = detect_credential(_DEFAULT_AUDIENCE)
if token is None:
return False
except GitHubOidcPermissionCredentialError:
# On GitHub Actions, forks do not have access to OIDC identities.
# We differentiate this case from other GitHub credential errors,
# since it's a case where we want to skip (i.e. return False).
if os.getenv("GITHUB_EVENT_NAME") == "pull_request":
return False
return True
except AmbientCredentialError:
# If ambient credential detection raises, then we *are* in an ambient
# environment but one that's been configured incorrectly. We
# pass this through, so that the CI fails appropriately rather than
# silently skipping the faulty tests.
return True

return True


def pytest_runtest_setup(item):
# Do we need a network connection?
online = False
for mark in ["online", "staging", "production"]:
if mark in item.keywords:
online = True

if online and item.config.getoption("--skip-online"):
pytest.skip(
"skipping test that requires network connectivity due to `--skip-online` flag"
)
elif "ambient_oidc" in item.keywords and not _has_oidc_id():
pytest.skip("skipping test that requires an ambient OIDC credential")

if "staging" in item.keywords and item.config.getoption("--skip-staging"):
pytest.skip(
"skipping test that requires staging infrastructure due to `--skip-staging` flag"
)


def pytest_configure(config):
config.addinivalue_line(
"markers", "staging: mark test as requiring Sigstore staging infrastructure"
)
config.addinivalue_line(
"markers",
"production: mark test as requiring Sigstore production infrastructure",
)
config.addinivalue_line(
"markers",
"online: mark test as requiring network connectivity (but not a specific Sigstore infrastructure)",
)
config.addinivalue_line(
"markers", "ambient_oidc: mark test as requiring an ambient OIDC identity"
)


@pytest.fixture
def asset():
def asset_integration(asset):
def _asset(name: str) -> Path:
return _ASSETS / name
return asset(f"integration/{name}")

return _asset

Expand Down
46 changes: 25 additions & 21 deletions test/integration/cli/test_attest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ def get_cli_params(
],
)
def test_attest_success_default_output_bundle(
capsys, sigstore, asset, predicate_type, predicate_filename
capsys, sigstore, asset_integration, predicate_type, predicate_filename
):
predicate_path = asset(f"attest/{predicate_filename}")
artifact = asset("a.txt")
predicate_path = asset_integration(f"attest/{predicate_filename}")
artifact = asset_integration("a.txt")
expected_output_bundle = artifact.with_name("a.txt.sigstore.json")

assert not expected_output_bundle.exists()
Expand Down Expand Up @@ -87,11 +87,13 @@ def test_attest_success_default_output_bundle(

@pytest.mark.staging
@pytest.mark.ambient_oidc
def test_attest_success_custom_output_bundle(capsys, sigstore, asset, tmp_path):
def test_attest_success_custom_output_bundle(
capsys, sigstore, asset_integration, tmp_path
):
predicate_type = PredicateType.SLSA_v0_2
predicate_filename = "slsa_predicate_v0_2.json"
predicate_path = asset(f"attest/{predicate_filename}")
artifact = asset("a.txt")
predicate_path = asset_integration(f"attest/{predicate_filename}")
artifact = asset_integration("a.txt")

output_bundle = tmp_path / "bundle.json"
assert not output_bundle.exists()
Expand All @@ -111,11 +113,13 @@ def test_attest_success_custom_output_bundle(capsys, sigstore, asset, tmp_path):

@pytest.mark.staging
@pytest.mark.ambient_oidc
def test_attest_overwrite_existing_bundle(capsys, sigstore, asset, tmp_path):
def test_attest_overwrite_existing_bundle(
capsys, sigstore, asset_integration, tmp_path
):
predicate_type = PredicateType.SLSA_v0_2
predicate_filename = "slsa_predicate_v0_2.json"
predicate_path = asset(f"attest/{predicate_filename}")
artifact = asset("a.txt")
predicate_path = asset_integration(f"attest/{predicate_filename}")
artifact = asset_integration("a.txt")

output_bundle = tmp_path / "bundle.json"
assert not output_bundle.exists()
Expand Down Expand Up @@ -148,11 +152,11 @@ def test_attest_overwrite_existing_bundle(capsys, sigstore, asset, tmp_path):
assert captures.out.endswith(f"Sigstore bundle written to {str(output_bundle)}\n")


def test_attest_invalid_predicate_type(capsys, sigstore, asset, tmp_path):
def test_attest_invalid_predicate_type(capsys, sigstore, asset_integration, tmp_path):
predicate_type = "invalid_type"
predicate_filename = "slsa_predicate_v0_2.json"
predicate_path = asset(f"attest/{predicate_filename}")
artifact = asset("a.txt")
predicate_path = asset_integration(f"attest/{predicate_filename}")
artifact = asset_integration("a.txt")

output_bundle = tmp_path / "bundle.json"
# On invalid argument errors we call `Argumentparser.error`, which prints
Expand All @@ -172,11 +176,11 @@ def test_attest_invalid_predicate_type(capsys, sigstore, asset, tmp_path):
assert captures.err.endswith(f"invalid PredicateType value: '{predicate_type}'\n")


def test_attest_mismatching_predicate(capsys, sigstore, asset, tmp_path):
def test_attest_mismatching_predicate(capsys, sigstore, asset_integration, tmp_path):
predicate_type = PredicateType.SLSA_v0_2
predicate_filename = "slsa_predicate_v1_0.json"
predicate_path = asset(f"attest/{predicate_filename}")
artifact = asset("a.txt")
predicate_path = asset_integration(f"attest/{predicate_filename}")
artifact = asset_integration("a.txt")

output_bundle = tmp_path / "bundle.json"
# On invalid argument errors we call `Argumentparser.error`, which prints
Expand All @@ -196,11 +200,11 @@ def test_attest_mismatching_predicate(capsys, sigstore, asset, tmp_path):
assert f'Unable to parse predicate of type "{predicate_type}":' in captures.err


def test_attest_missing_predicate(capsys, sigstore, asset, tmp_path):
def test_attest_missing_predicate(capsys, sigstore, asset_integration, tmp_path):
predicate_type = PredicateType.SLSA_v0_2
predicate_filename = "doesnt_exist.json"
predicate_path = asset(f"attest/{predicate_filename}")
artifact = asset("a.txt")
predicate_path = asset_integration(f"attest/{predicate_filename}")
artifact = asset_integration("a.txt")

output_bundle = tmp_path / "bundle.json"
# On invalid argument errors we call `Argumentparser.error`, which prints
Expand All @@ -220,10 +224,10 @@ def test_attest_missing_predicate(capsys, sigstore, asset, tmp_path):
assert captures.err.endswith(f"Predicate must be a file: {predicate_path}\n")


def test_attest_invalid_json_predicate(capsys, sigstore, asset, tmp_path):
def test_attest_invalid_json_predicate(capsys, sigstore, asset_integration, tmp_path):
predicate_type = PredicateType.SLSA_v0_2
predicate_path = asset("a.txt")
artifact = asset("a.txt")
predicate_path = asset_integration("a.txt")
artifact = asset_integration("a.txt")

output_bundle = tmp_path / "bundle.json"
# On invalid argument errors we call `Argumentparser.error`, which prints
Expand Down
8 changes: 4 additions & 4 deletions test/integration/cli/test_plumbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
from sigstore.verify.verifier import Verifier


def test_fix_bundle_fixes_missing_checkpoint(capsys, sigstore, asset):
invalid_bundle = asset("Python-3.12.5.tgz.sigstore")
def test_fix_bundle_fixes_missing_checkpoint(capsys, sigstore, asset_integration):
invalid_bundle = asset_integration("Python-3.12.5.tgz.sigstore")

# The bundle is invalid, because it's missing a checkpoint
# for its inclusion proof.
Expand Down Expand Up @@ -64,8 +64,8 @@ def test_fix_bundle_fixes_missing_checkpoint(capsys, sigstore, asset):
)


def test_fix_bundle_upgrades_bundle(capsys, sigstore, asset):
invalid_bundle = asset("Python-3.12.5.tgz.sigstore")
def test_fix_bundle_upgrades_bundle(capsys, sigstore, asset_integration):
invalid_bundle = asset_integration("Python-3.12.5.tgz.sigstore")

# Running `sigstore plumbing fix-bundle --upgrade-version`
# emits a fixed bundle.
Expand Down
Loading

0 comments on commit 47e8359

Please sign in to comment.