Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deduplicate test fixtures #1137

Merged
merged 3 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Loading