Skip to content

Commit

Permalink
Merge pull request #20 from ymyzk/env-support
Browse files Browse the repository at this point in the history
Specify factors using environment variables
  • Loading branch information
ymyzk authored Mar 22, 2020
2 parents 1eef973 + d708233 commit db91d92
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 12 deletions.
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,71 @@ python =
[testenv]
...
```

You can also use environment variable to decide which environment to run.
The following is an example to install different dependency based on platform.
It will create 12 jobs when running the workflow on GitHub Actions.
- On Python 2.7/ubuntu-latest job, tox runs `py27-linux` environment
- On Python 3.5/ubuntu-latest job, tox runs `py35-linux` environment
- and so on.

`.github/workflows/<workflow>.yml`:
```yaml
name: Python package
on: [push]
jobs:
build:
runs-on: ${{ matrix.platform }}
strategy:
max-parallel: 4
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
python-version: [2.7, 3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Test with tox
run: tox
env:
PLATFORM: ${{ matrix.platform }}
```

`tox.ini`:
```ini
[tox]
envlist = py{27,36,37,38}-{linux,macos,windows}
[gh-actions]
python =
2.7: py27
3.8: py38, mypy
pypy2: pypy2
pypy3: pypy3
[gh-actions:env]
PLATFORM =
ubuntu-latest: linux
macos-latest: macos
windows-latest: windows
[testenv]
deps =
<common dependency>
linux: <Linux specific deps>
macos: <macOS specific deps>
windows: <Windows specific deps>
...
```

See [tox's documentation about factor-conditional settings](https://tox.readthedocs.io/en/latest/config.html#factors-and-factor-conditional-settings) as well.

28 changes: 21 additions & 7 deletions src/tox_gh_actions/plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from itertools import product
import os
import sys
from typing import Dict, Iterable, List
from typing import Any, Dict, Iterable, List

import pluggy
from tox.config import Config, _split_env as split_env
Expand All @@ -20,7 +21,7 @@ def tox_configure(config):
version = get_python_version()
verbosity2("Python version: {}".format(version))

gh_actions_config = parse_config(config._cfg.sections.get("gh-actions", {}))
gh_actions_config = parse_config(config._cfg.sections)
verbosity2("tox-gh-actions config: {}".format(gh_actions_config))

factors = get_factors(gh_actions_config, version)
Expand All @@ -35,20 +36,33 @@ def tox_configure(config):


def parse_config(config):
# type: (Dict[str, str]) -> Dict[str, Dict[str, List[str]]]
# type: (Dict[str, Dict[str, str]]) -> Dict[str, Dict[str, Any]]
"""Parse gh-actions section in tox.ini"""
config_python = parse_dict(config.get("python", ""))
config_python = parse_dict(config.get("gh-actions", {}).get("python", ""))
config_env = {
name: {k: split_env(v) for k, v in parse_dict(conf).items()}
for name, conf in config.get("gh-actions:env", {}).items()
}
# Example of split_env:
# "py{27,38}" => ["py27", "py38"]
return {
"python": {k: split_env(v) for k, v in config_python.items()}
"python": {k: split_env(v) for k, v in config_python.items()},
"env": config_env,
}


def get_factors(gh_actions_config, version):
# type: (Dict[str, Dict[str, List[str]]], str) -> List[str]
# type: (Dict[str, Dict[str, Any]], str) -> List[str]
"""Get a list of factors"""
return gh_actions_config["python"].get(version, [])
factors = [] # type: List[List[str]]
if version in gh_actions_config["python"]:
factors.append(gh_actions_config["python"][version])
for env, env_config in gh_actions_config.get("env", {}).items():
if env in os.environ:
env_value = os.environ[env]
if env_value in env_config:
factors.append(env_config[env_value])
return [x for x in map(lambda f: "-".join(f), product(*factors)) if x]


def get_envlist_from_factors(envlist, factors):
Expand Down
181 changes: 176 additions & 5 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
@pytest.mark.parametrize("config,expected", [
(
{
"python": """2.7: py27
"gh-actions": {
"python": """2.7: py27
3.5: py35
3.6: py36
3.7: py37, flake8"""
}
},
{
"python": {
Expand All @@ -18,18 +20,55 @@
"3.6": ["py36"],
"3.7": ["py37", "flake8"],
},
"env": {},
},
),
(
{
"gh-actions": {
"python": """2.7: py27
3.8: py38"""
},
"gh-actions:env": {
"PLATFORM": """ubuntu-latest: linux
macos-latest: macos
windows-latest: windows"""
}
},
{
"python": {
"2.7": ["py27"],
"3.8": ["py38"],
},
"env": {
"PLATFORM": {
"ubuntu-latest": ["linux"],
"macos-latest": ["macos"],
"windows-latest": ["windows"],
},
},
},
),
(
{"gh-actions": {}},
{
"python": {},
"env": {},
},
),
(
{},
{"python": {}},
{
"python": {},
"env": {},
},
),
])
def test_parse_config(config, expected):
assert plugin.parse_config(config) == expected


@pytest.mark.parametrize("config,factors,expected", [
@pytest.mark.parametrize("config,version,environ,expected", [
(
{
"python": {
Expand All @@ -39,27 +78,151 @@ def test_parse_config(config, expected):
"unknown": {},
},
"2.7",
{},
["py27", "flake8"],
),
(
{
"python": {
"2.7": ["py27", "flake8"],
"3.8": ["py38", "flake8"],
},
"env": {
"SAMPLE": {
"VALUE1": ["fact1", "fact2"],
"VALUE2": ["fact3", "fact4"],
},
},
},
"2.7",
{
"SAMPLE": "VALUE1",
"HOGE": "VALUE3",
},
["py27-fact1", "py27-fact2", "flake8-fact1", "flake8-fact2"],
),
(
{
"python": {
"2.7": ["py27", "flake8"],
"3.8": ["py38", "flake8"],
},
"env": {
"SAMPLE": {
"VALUE1": ["fact1", "fact2"],
"VALUE2": ["fact3", "fact4"],
},
"HOGE": {
"VALUE3": ["fact5", "fact6"],
"VALUE4": ["fact7", "fact8"],
},
},
},
"2.7",
{
"SAMPLE": "VALUE1",
"HOGE": "VALUE3",
},
[
"py27-fact1-fact5", "py27-fact1-fact6",
"py27-fact2-fact5", "py27-fact2-fact6",
"flake8-fact1-fact5", "flake8-fact1-fact6",
"flake8-fact2-fact5", "flake8-fact2-fact6",
],
),
(
{
"python": {
"2.7": ["py27", "flake8"],
"3.8": ["py38", "flake8"],
},
"env": {
"SAMPLE": {
"VALUE1": ["django18", "flake8"],
"VALUE2": ["django18"],
},
},
},
"2.7",
{
"SAMPLE": "VALUE1",
"HOGE": "VALUE3",
},
[
"py27-django18", "py27-flake8",
"flake8-django18", "flake8-flake8",
],
),
(
{
"python": {
"2.7": ["py27", "flake8"],
"3.8": ["py38", "flake8"],
},
"env": {
"SAMPLE": {
"VALUE1": ["fact1", "fact2"],
"VALUE2": ["fact3", "fact4"],
},
},
"unknown": {},
},
"2.7",
{
"SAMPLE": "VALUE3",
},
["py27", "flake8"],
),
(
{
"python": {
"2.7": ["py27", "flake8"],
"3.8": ["py38", "flake8"],
},
"env": {
"SAMPLE": {
"VALUE1": [],
},
},
"unknown": {},
},
"3.8",
{
"SAMPLE": "VALUE2",
},
["py38", "flake8"],
),
(
{
"python": {
"3.8": ["py38", "flake8"],
},
},
"2.7",
{},
[],
),
(
{
"python": {},
},
"3.8",
{},
[],
),
])
def test_get_factors(config, factors, expected):
assert plugin.get_factors(config, factors) == expected
def test_get_factors(mocker, config, version, environ, expected):
mocker.patch("tox_gh_actions.plugin.os.environ", environ)
result = normalize_factors_list(plugin.get_factors(config, version))
expected = normalize_factors_list(expected)
assert result == expected


def normalize_factors_list(factors):
"""Utility to make it compare equality of a list of factors"""
result = [tuple(sorted(f.split("-"))) for f in factors]
result.sort()
return result


@pytest.mark.parametrize("envlist,factors,expected", [
Expand All @@ -83,6 +246,14 @@ def test_get_factors(config, factors, expected):
['py37', 'flake8'],
['py37-dj111', 'py37-dj20', 'flake8'],
),
(
['py27-django18', 'py37-django18', 'flake8'],
[
'py27-django18', 'py27-flake8',
'flake8-django18', 'flake8-flake8',
],
['py27-django18', 'flake8'],
)
])
def test_get_envlist_from_factors(envlist, factors, expected):
assert plugin.get_envlist_from_factors(envlist, factors) == expected
Expand Down

0 comments on commit db91d92

Please sign in to comment.