Goal: Implement the matrix expansion behaviour in matrix_combinations()
function, following the official Github Actions documentation.
Run tests:
pytest main.py
Use on real workflow file:
python main.py .github/workflows/CI.yml
Goal: Implement the matrix expansion behaviour in matrix_combinations()
function, following the official Github Actions documentation.
Run tests:
pytest main.py
Use on real workflow file:
python main.py .github/workflows/CI.yml
import sys | |
def matrix_combinations(matrix) -> list[dict]: | |
""" | |
https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#expanding-or-adding-matrix-configurations | |
For each object in the include list, the key:value pairs in the object will | |
be added to each of the matrix combinations if none of the key:value pairs | |
overwrite any of the original matrix values. If the object cannot be added | |
to any of the matrix combinations, a new matrix combination will be created | |
instead. Note that the original matrix values will not be overwritten, but | |
added matrix values can be overwritten. | |
""" | |
return [] | |
def test_include_only(): | |
matrix = { | |
"include": [ | |
{"a": 42}, | |
{"a": "foo"}, | |
] | |
} | |
expected = [ | |
{"a": 42}, | |
{"a": "foo"}, | |
] | |
assert matrix_combinations(matrix) == expected | |
def test_exclude(): | |
matrix = { | |
"a": [1, 2], | |
"b": [3, 4], | |
"exclude": [ | |
{"a": 1}, | |
{"a": 2, "b": 4}, | |
], | |
} | |
expected = [ | |
{"a": 2, "b": 3}, | |
] | |
assert matrix_combinations(matrix) == expected | |
def test_official_documentation_example(): | |
matrix = { | |
"fruit": ["apple", "pear"], | |
"animal": ["cat", "dog"], | |
"include": [ | |
{"color": "green"}, | |
{"color": "pink", "animal": "cat"}, | |
{"fruit": "apple", "shape": "circle"}, | |
{"fruit": "banana"}, | |
{"fruit": "banana", "animal": "cat"}, | |
], | |
} | |
expected = [ | |
{"fruit": "apple", "animal": "cat", "color": "pink", "shape": "circle"}, | |
{"fruit": "apple", "animal": "dog", "color": "green", "shape": "circle"}, | |
{"fruit": "pear", "animal": "cat", "color": "pink"}, | |
{"fruit": "pear", "animal": "dog", "color": "green"}, | |
{"fruit": "banana"}, | |
{"fruit": "banana", "animal": "cat"}, | |
] | |
# * {color: green} is added to all of the original matrix combinations because | |
# it can be added without overwriting any part of the original | |
# combinations. | |
# * {color: pink, animal: cat} adds color:pink only to the | |
# original matrix combinations that include animal: cat. This overwrites | |
# the color: green that was added by the previous include entry. | |
# * {fruit: apple, shape: circle} adds shape: circle only to the original | |
# matrix combinations that include fruit: apple. | |
# * {fruit: banana} cannot be | |
# added to any original matrix combination without overwriting a value, so | |
# it is added as an additional matrix combination. | |
# * {fruit: banana, animal: cat} cannot be added to any original matrix combination | |
# without overwriting a value, so it is added as an additional matrix combination. | |
# It does not add to the {fruit: banana} matrix combination because that | |
# combination was not one of the original matrix combinations. | |
combinations = matrix_combinations(matrix) | |
for combination in expected: | |
assert combination in combinations | |
combinations.remove(combination) | |
assert len(combinations) == 0 | |
if __name__ == "__main__": | |
try: | |
import yaml | |
except ImportError: | |
print("Run `pip install pyyaml`") | |
sys.exit(1) | |
with open(sys.argv[1]) as f: | |
content = yaml.safe_load(f) | |
for name, job in content["jobs"].items(): | |
job_name = job.get("name", name) | |
interpolated = re.findall("\\$\\{\\{([^\\}]+)\\}\\}", job_name) | |
matrix = job.get("strategy", {}).get("matrix") | |
if not matrix: | |
continue | |
combinations = matrix_combinations(matrix) | |
for combination in combinations: | |
for var in interpolated: | |
_, field = var.split(".") # ${{ matrix.{var} }} | |
value = combination.get(field.strip(), "") | |
job_name = job_name.replace(f"${{{{{var}}}}}", str(value)) | |
print(f"\n- {job_name}") | |
for var, value in combination.items(): | |
print(f" {var}: {value}") |