Skip to content

Commit 5e59ee7

Browse files
committed
Rework python version handling associated testconfig path handling.
A previous revision fixed an immediate problem with test path handling but knowingly left some things on the table - in particular a split between container based handling of Python 2 and the local execution of Python 3 (meaning a potentially inconsistent version relative to what the project considers officially supported). Address this entirely: rework the default behaviour of `make test` to container based execution and use a consistent baseline Python 3. In order to continue to support rapid local iteration, provide a separate `make unittest` target which will execute the test suite locally and is also used as a mechanism to run other supporting tools. The clean use of containers necessitated various changes that make path arguments within the testconfig non-overlapping and better isolated. Adjust all paths generated within the test suite to be always from the base root instead of relative the output directory. Opt to patch the makeconfig generator rather than fiddle with generateconfs again. While here also add support for an environment variable overried that allows execution of the test suite against arbitrary python 3 versions.
1 parent a146e1b commit 5e59ee7

18 files changed

+352
-150
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
make dependencies
3636
- name: Run tests
3737
run: |
38-
make test
38+
make unittest
3939
4040
python3-rocky9ish:
4141
runs-on: ubuntu-22.04
@@ -51,7 +51,7 @@ jobs:
5151
make dependencies
5252
- name: Run tests
5353
run: |
54-
make test
54+
make unittest
5555
5656
python3-rocky8ish:
5757
runs-on: ubuntu-20.04
@@ -67,7 +67,7 @@ jobs:
6767
make dependencies
6868
- name: Run tests
6969
run: |
70-
make test
70+
make unittest
7171
7272
python2-latest:
7373
runs-on: ubuntu-latest
@@ -80,8 +80,7 @@ jobs:
8080
uses: actions/checkout@v4
8181
- name: Setup environment
8282
run: |
83-
pip install --no-cache-dir -r requirements.txt -r local-requirements.txt
83+
make PYTHON_BIN=python PY=2 dependencies
8484
- name: Run tests
8585
run: |
86-
PYTHON_BIN=python ./envhelp/makeconfig test --python2
87-
MIG_ENV='local' python -m unittest discover -s tests/
86+
make PYTHON_BIN=python PY=2 unittest

Makefile

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
ifndef MIG_ENV
22
MIG_ENV = 'local'
33
endif
4-
ifeq ($(PY),2)
4+
5+
ifndef PY
6+
PY = 3
7+
endif
8+
9+
LOCAL_PYTHON_BIN = './envhelp/lpython'
10+
11+
ifdef PYTHON_BIN
12+
LOCAL_PYTHON_BIN = $(PYTHON_BIN)
13+
else ifeq ($(PY),2)
514
PYTHON_BIN = './envhelp/python2'
615
else
716
PYTHON_BIN = './envhelp/python3'
817
endif
18+
919
ifeq ($(ALLDEPS),1)
1020
REQS_PATH = ./recommended.txt
1121
else
@@ -17,21 +27,23 @@ info:
1727
@echo
1828
@echo "The following should help you get started:"
1929
@echo
20-
@echo "'make test' - run the test suite"
21-
@echo "'make PY=2 test' - run the test suite (python 2)"
30+
@echo "'make test' - run the test suite (default python 3)"
31+
@echo "'make PY=2 test' - run the test suite (default python 2)"
32+
@echo "'make unittest' - execute tests locally for development"
2233

2334
.PHONY: fmt
2435
fmt:
2536
ifneq ($(MIG_ENV),'local')
2637
@echo "unavailable outside local development environment"
2738
@exit 1
2839
endif
29-
$(PYTHON_BIN) -m autopep8 --ignore E402 -i
40+
$(LOCAL_PYTHON_BIN) -m autopep8 --ignore E402 -i
3041

3142
.PHONY: clean
3243
clean:
3344
@rm -f ./envhelp/py2.imageid
34-
@rm -f ./envhelp/py3.depends
45+
@rm -f ./envhelp/py3.imageid
46+
@rm -f ./envhelp/local.depends
3547

3648
.PHONY: distclean
3749
distclean: clean
@@ -44,37 +56,41 @@ distclean: clean
4456
test: dependencies testconfig
4557
@$(PYTHON_BIN) -m unittest discover -s tests/
4658

59+
.PHONY: unittest
60+
unittest: dependencies testconfig
61+
@$(LOCAL_PYTHON_BIN) -m unittest discover -s tests/
62+
4763
.PHONY: dependencies
48-
dependencies: ./envhelp/venv/pyvenv.cfg ./envhelp/py3.depends
64+
ifeq ($(PY),2)
65+
dependencies: ./envhelp/local.depends
66+
else
67+
dependencies: ./envhelp/venv/pyvenv.cfg ./envhelp/local.depends
68+
endif
4969

5070
.PHONY: testconfig
5171
testconfig: ./envhelp/output/testconfs
5272

5373
./envhelp/output/testconfs:
54-
@./envhelp/makeconfig test --python2
74+
@./envhelp/makeconfig test --docker
5575
@./envhelp/makeconfig test
56-
@mkdir -p ./envhelp/output/certs
57-
@mkdir -p ./envhelp/output/state
58-
@mkdir -p ./envhelp/output/state/log
5976

6077
ifeq ($(MIG_ENV),'local')
61-
./envhelp/py3.depends: $(REQS_PATH) local-requirements.txt
78+
./envhelp/local.depends: $(REQS_PATH) local-requirements.txt
6279
else
63-
./envhelp/py3.depends: $(REQS_PATH)
80+
./envhelp/local.depends: $(REQS_PATH)
6481
endif
65-
@rm -f ./envhelp/py3.depends
66-
@echo "upgrading venv pip as required for some dependencies"
67-
@./envhelp/venv/bin/pip3 install --upgrade pip
6882
@echo "installing dependencies from $(REQS_PATH)"
69-
@./envhelp/venv/bin/pip3 install -r $(REQS_PATH)
83+
@$(LOCAL_PYTHON_BIN) -m pip install -r $(REQS_PATH)
7084
ifeq ($(MIG_ENV),'local')
7185
@echo ""
7286
@echo "installing development dependencies"
73-
@./envhelp/venv/bin/pip3 install -r local-requirements.txt
87+
@$(LOCAL_PYTHON_BIN) -m pip install -r local-requirements.txt
7488
endif
75-
@touch ./envhelp/py3.depends
89+
@touch ./envhelp/local.depends
7690

7791
./envhelp/venv/pyvenv.cfg:
7892
@echo "provisioning environment"
7993
@/usr/bin/env python3 -m venv ./envhelp/venv
80-
@rm -f ./envhelp/py3.depends
94+
@rm -f ./envhelp/local.depends
95+
@echo "upgrading venv pip as required for some dependencies"
96+
@./envhelp/venv/bin/pip3 install --upgrade pip
File renamed without changes.

envhelp/docker/Dockerfile.py3

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM python:3.9
2+
3+
WORKDIR /usr/src/app
4+
5+
COPY requirements.txt local-requirements.txt ./
6+
RUN pip install --no-cache-dir -r requirements.txt -r local-requirements.txt
7+
8+
CMD [ "python", "--version" ]

envhelp/docker/Dockerfile.pyver

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
ARG pyver
2+
FROM python:${pyver}
3+
4+
WORKDIR /usr/src/app
5+
6+
COPY requirements.txt local-requirements.txt ./
7+
RUN pip install --no-cache-dir -r requirements.txt -r local-requirements.txt
8+
9+
CMD [ "python", "--version" ]

envhelp/dpython

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/bin/sh
2+
#
3+
# --- BEGIN_HEADER ---
4+
#
5+
# dpython - wrapper to invoke a containerised python
6+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
7+
#
8+
# This file is part of MiG.
9+
#
10+
# MiG is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# MiG is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
23+
# USA.
24+
#
25+
# --- END_HEADER ---
26+
#
27+
28+
set -e
29+
30+
SCRIPT_PATH=$(realpath "$0")
31+
SCRIPT_BASE=$(dirname -- "$SCRIPT_PATH")
32+
MIG_BASE=$(realpath "$SCRIPT_BASE/..")
33+
34+
if [ -n "${PY}" ]; then
35+
PYVER="$PY"
36+
PYTHON_SUFFIX="py$PY"
37+
DOCKER_FILE_SUFFIX="$PYTHON_SUFFIX"
38+
elif [ -n "${PYVER}" ]; then
39+
PY=3
40+
PYTHON_SUFFIX="pyver-$PYVER"
41+
DOCKER_FILE_SUFFIX="pyver"
42+
else
43+
echo "No python version specified - please supply a PY env var"
44+
exit 1
45+
fi
46+
47+
DOCKER_FILE="$SCRIPT_BASE/docker/Dockerfile.$DOCKER_FILE_SUFFIX"
48+
DOCKER_IMAGEID_FILE="$SCRIPT_BASE/$PYTHON_SUFFIX.imageid"
49+
50+
# NOTE: portable dynamic lookup with docker as default and fallback to podman
51+
DOCKER_BIN=$(command -v docker || command -v podman || echo "")
52+
if [ -z "${DOCKER_BIN}" ]; then
53+
echo "No docker binary found - cannot use for python $PY tests"
54+
exit 1
55+
fi
56+
57+
# default PYTHONPATH such that directly executing files in the repo "just works"
58+
# NOTE: this is hard-coded to the mount point used within the container
59+
PYTHONPATH='/usr/src/app'
60+
61+
# default any variables for container development
62+
MIG_ENV=${MIG_ENV:-'docker'}
63+
64+
# determine if the image has changed
65+
echo -n "validating python $PY container.. "
66+
67+
# load a previously written docker image id if present
68+
IMAGEID_STORED=$(cat "$DOCKER_IMAGEID_FILE" 2>/dev/null || echo "")
69+
70+
IMAGEID=$(${DOCKER_BIN} build -f "$DOCKER_FILE" . -q --build-arg "pyver=$PYVER")
71+
if [ "$IMAGEID" != "$IMAGEID_STORED" ]; then
72+
echo "rebuilt for changes"
73+
74+
# reset the image id so the next call finds no changes
75+
echo "$IMAGEID" > "$DOCKER_IMAGEID_FILE"
76+
else
77+
echo "no changes needed"
78+
fi
79+
80+
echo "using image id $IMAGEID"
81+
82+
# execute python2 within the image passing the supplied arguments
83+
84+
${DOCKER_BIN} run -it --rm \
85+
--mount "type=bind,source=$MIG_BASE,target=/usr/src/app" \
86+
--env "PYTHONPATH=$PYTHONPATH" \
87+
--env "MIG_ENV=$MIG_ENV" \
88+
"$IMAGEID" python$PY $@

envhelp/lpython

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/sh
2+
#
3+
# --- BEGIN_HEADER ---
4+
#
5+
# python3 - wrapper to invoke a local python3 virtual environment
6+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
7+
#
8+
# This file is part of MiG.
9+
#
10+
# MiG is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# MiG is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
23+
# USA.
24+
#
25+
# --- END_HEADER ---
26+
#
27+
28+
set -e
29+
30+
SCRIPT_PATH=$(realpath "$0")
31+
SCRIPT_BASE=$(dirname -- "$SCRIPT_PATH")
32+
MIG_BASE=$(realpath "$SCRIPT_BASE/..")
33+
34+
PYTHON_BIN=${PYTHON_BIN:-"$SCRIPT_BASE/venv/bin/python3"}
35+
if [ ! -f "${PYTHON_BIN}" ]; then
36+
echo "No python binary found - perhaps the virtual env was not created"
37+
exit 1
38+
fi
39+
40+
# default PYTHONPATH such that directly executing files in the repo "just works"
41+
PYTHONPATH=${PYTHONPATH:-"$MIG_BASE"}
42+
43+
# default any variables for local development
44+
MIG_ENV=${MIG_ENV:-'local'}
45+
46+
PYTHONPATH="$PYTHONPATH" MIG_ENV="$MIG_ENV" "$PYTHON_BIN" "$@"

envhelp/makeconfig.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@
3838

3939
from mig.shared.install import MIG_BASE, generate_confs
4040

41-
_LOCAL_ENVHELP_OUTPUT_DIR = os.path.realpath(
42-
os.path.join(os.path.dirname(__file__), "output"))
41+
_LOCAL_MIG_BASE = os.path.normpath(
42+
os.path.join(os.path.dirname(__file__), ".."))
43+
_LOCAL_ENVHELP_OUTPUT_DIR = os.path.join(_LOCAL_MIG_BASE, "envhelp/output")
4344
_MAKECONFIG_ALLOWED = ["local", "test"]
4445

4546

@@ -51,21 +52,27 @@ def _at(sequence, index=-1, default=None):
5152
return default
5253

5354

54-
def write_testconfig(env_name, is_py2=False):
55-
confs_name = 'confs' if env_name == 'local' else '%sconfs' % (env_name,)
56-
confs_suffix = 'py2' if is_py2 else 'py3'
55+
def write_testconfig(env_name, is_docker=False):
56+
is_predefined = env_name == 'test'
57+
confs_name = '%sconfs' % (env_name,)
58+
if is_predefined:
59+
confs_suffix = 'docker' if is_docker else 'local'
60+
else:
61+
confs_suffix = 'py3'
5762

5863
overrides = {
5964
'destination': os.path.join(_LOCAL_ENVHELP_OUTPUT_DIR, confs_name),
6065
'destination_suffix': "-%s" % (confs_suffix,),
6166
}
6267

63-
# determine the paths by which we will access the various configured dirs
64-
if is_py2:
68+
# determine the paths b which we will access the various configured dirs
69+
# the tests output directory - when invoked within
70+
71+
if is_predefined and is_docker:
6572
env_mig_base = '/usr/src/app'
6673
else:
67-
env_mig_base = MIG_BASE
68-
conf_dir_path = os.path.join(env_mig_base, "envhelp/output")
74+
env_mig_base = _LOCAL_MIG_BASE
75+
conf_dir_path = os.path.join(env_mig_base, "tests/output")
6976

7077
overrides.update(**{
7178
'mig_code': os.path.join(conf_dir_path, 'mig'),
@@ -85,7 +92,7 @@ def write_testconfig(env_name, is_py2=False):
8592

8693
def main_(argv):
8794
env_name = _at(argv, index=1, default='')
88-
arg_is_py2 = '--python2' in argv
95+
arg_is_docker = '--docker' in argv
8996

9097
if env_name == '':
9198
raise RuntimeError(
@@ -94,7 +101,7 @@ def main_(argv):
94101
raise RuntimeError('environment must be one of %s' %
95102
(_MAKECONFIG_ALLOWED,))
96103

97-
write_testconfig(env_name, is_py2=arg_is_py2)
104+
write_testconfig(env_name, is_docker=arg_is_docker)
98105

99106

100107
def main(argv=sys.argv):

0 commit comments

Comments
 (0)