Skip to content

Commit

Permalink
test: fix the GitHub Actions workflow detection test (#591)
Browse files Browse the repository at this point in the history
This PR fixes a bug in the GitHub Actions workflow detection test. The assertions in the test_get_workflows function were checking the return values of list.sort(), which is an in-place function and doesn't return anything. Instead we should compare the sorted lists.

This PR also refactors the tests and removes MacaronTestCase, which is not necessary anymore.

Signed-off-by: behnazh-w <behnaz.hassanshahi@oracle.com>
  • Loading branch information
behnazh-w authored Jan 5, 2024
1 parent d14ad94 commit f31e56a
Showing 1 changed file with 94 additions and 76 deletions.
170 changes: 94 additions & 76 deletions tests/slsa_analyzer/ci_service/test_github_actions.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,105 @@
# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

"""This module tests GitHub Actions CI service."""

import os
from pathlib import Path

import pytest

from macaron import MACARON_PATH
from macaron.code_analyzer.call_graph import CallGraph
from macaron.parsers.actionparser import parse as parse_action
from macaron.slsa_analyzer.ci_service.github_actions import GHWorkflowType, GitHubActions, GitHubNode

from ...macaron_testcase import MacaronTestCase


class TestGitHubActions(MacaronTestCase):
"""Test the GitHub Actions CI service."""

github_actions = GitHubActions()
mock_repos = Path(__file__).parent.joinpath("mock_repos")
ga_has_build_kws = mock_repos.joinpath("has_build_gh_actions")
jenkins_build = mock_repos.joinpath("has_build_jenkins")
ga_no_build_kws = mock_repos.joinpath("no_build_gh_actions")

def test_build_call_graph(self) -> None:
"""Test building call graphs for GitHub Actions workflows."""
resources_dir = Path(__file__).parent.joinpath("resources", "github")

# Test internal and reusable workflows.
# Parse GitHub Actions workflows.
root = GitHubNode(name="root", node_type=GHWorkflowType.NONE, source_path="", parsed_obj={}, caller_path="")
gh_cg = CallGraph(root, "")
workflow_path = os.path.join(resources_dir, "valid1.yaml")
parsed_obj = parse_action(workflow_path, macaron_path=str(self.macaron_path))

callee = GitHubNode(
name=os.path.basename(workflow_path),
node_type=GHWorkflowType.INTERNAL,
source_path=workflow_path,
parsed_obj=parsed_obj,
caller_path="",
)
root.add_callee(callee)
self.github_actions.build_call_graph_from_node(callee)
assert [
"GitHubNode(valid1.yaml,GHWorkflowType.INTERNAL)",
"GitHubNode(apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v2,GHWorkflowType.REUSABLE)",
] == [str(node) for node in gh_cg.bfs()]

# Test internal and external workflows.
# Parse GitHub Actions workflows.
root = GitHubNode(name="root", node_type=GHWorkflowType.NONE, source_path="", parsed_obj={}, caller_path="")
gh_cg = CallGraph(root, "")
workflow_path = os.path.join(resources_dir, "valid2.yaml")
parsed_obj = parse_action(workflow_path, macaron_path=str(self.macaron_path))

callee = GitHubNode(
name=os.path.basename(workflow_path),
node_type=GHWorkflowType.INTERNAL,
source_path=workflow_path,
parsed_obj=parsed_obj,
caller_path="",
)
root.add_callee(callee)
self.github_actions.build_call_graph_from_node(callee)
assert [
"GitHubNode(valid2.yaml,GHWorkflowType.INTERNAL)",
"GitHubNode(actions/checkout@v3,GHWorkflowType.EXTERNAL)",
"GitHubNode(actions/cache@v3,GHWorkflowType.EXTERNAL)",
"GitHubNode(actions/setup-java@v3,GHWorkflowType.EXTERNAL)",
] == [str(node) for node in gh_cg.bfs()]

def test_is_detected(self) -> None:
"""Test detecting GitHub Action config files."""
assert self.github_actions.is_detected(str(self.ga_has_build_kws))
assert self.github_actions.is_detected(str(self.ga_no_build_kws))
assert not self.github_actions.is_detected(str(self.jenkins_build))

def test_get_workflows(self) -> None:
"""Test getting GitHub Actions workflows."""
expect = list(self.ga_has_build_kws.joinpath(".github", "workflows").glob("*")).sort()
assert self.github_actions.get_workflows(str(self.ga_has_build_kws)).sort() == expect

expect = list(self.ga_no_build_kws.joinpath(".github", "workflows").glob("*")).sort()
assert self.github_actions.get_workflows(str(self.ga_no_build_kws)).sort() == expect

assert not self.github_actions.get_workflows(str(self.jenkins_build))
mock_repos = Path(__file__).parent.joinpath("mock_repos")
ga_has_build_kws = mock_repos.joinpath("has_build_gh_actions")
jenkins_build = mock_repos.joinpath("has_build_jenkins")
ga_no_build_kws = mock_repos.joinpath("no_build_gh_actions")


@pytest.fixture(name="github_actions")
def github_actions_() -> GitHubActions:
"""Create a GitHubActions instance."""
return GitHubActions()


@pytest.mark.parametrize(
(
"workflow_name",
"expect",
),
[
(
"valid1.yaml",
[
"GitHubNode(valid1.yaml,GHWorkflowType.INTERNAL)",
"GitHubNode(apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v2,GHWorkflowType.REUSABLE)",
],
),
(
"valid2.yaml",
[
"GitHubNode(valid2.yaml,GHWorkflowType.INTERNAL)",
"GitHubNode(actions/checkout@v3,GHWorkflowType.EXTERNAL)",
"GitHubNode(actions/cache@v3,GHWorkflowType.EXTERNAL)",
"GitHubNode(actions/setup-java@v3,GHWorkflowType.EXTERNAL)",
],
),
],
ids=[
"Internal and reusable workflows",
"Internal and external workflows",
],
)
def test_build_call_graph(github_actions: GitHubActions, workflow_name: str, expect: list[str]) -> None:
"""Test building call graphs for GitHub Actions workflows."""
resources_dir = Path(__file__).parent.joinpath("resources", "github")

# Parse GitHub Actions workflows.
root = GitHubNode(name="root", node_type=GHWorkflowType.NONE, source_path="", parsed_obj={}, caller_path="")
gh_cg = CallGraph(root, "")
workflow_path = os.path.join(resources_dir, workflow_name)
parsed_obj = parse_action(workflow_path, macaron_path=MACARON_PATH)

callee = GitHubNode(
name=os.path.basename(workflow_path),
node_type=GHWorkflowType.INTERNAL,
source_path=workflow_path,
parsed_obj=parsed_obj,
caller_path="",
)
root.add_callee(callee)
github_actions.build_call_graph_from_node(callee)
assert [str(node) for node in gh_cg.bfs()] == expect


def test_is_detected(github_actions: GitHubActions) -> None:
"""Test detecting GitHub Action config files."""
assert github_actions.is_detected(str(ga_has_build_kws))
assert github_actions.is_detected(str(ga_no_build_kws))
assert not github_actions.is_detected(str(jenkins_build))


@pytest.mark.parametrize(
"mock_repo",
[
ga_has_build_kws,
ga_no_build_kws,
],
ids=[
"GH Actions with build",
"GH Actions with no build",
],
)
def test_gh_get_workflows(github_actions: GitHubActions, mock_repo: Path) -> None:
"""Test detection of reachable GitHub Actions workflows."""
expect = [str(path) for path in mock_repo.joinpath(".github", "workflows").glob("*")]
workflows = github_actions.get_workflows(str(mock_repo))
assert sorted(workflows) == sorted(expect)


def test_gh_get_workflows_fail_on_jenkins(github_actions: GitHubActions) -> None:
"""Assert GitHubActions workflow detection not working on Jenkins CI configuration files."""
assert not github_actions.get_workflows(str(jenkins_build))

0 comments on commit f31e56a

Please sign in to comment.