Skip to content

Commit a256231

Browse files
authored
Enable jenkins CI (#209)
Enabled CI with Jenkins
1 parent e00c950 commit a256231

14 files changed

+283
-23
lines changed

.coveragerc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[run]
2+
source = udsoncan
3+
concurrency = thread
4+

Dockerfile

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
FROM ubuntu:20.04 as build-tests
2+
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
5+
RUN apt-get update && apt-get install -y \
6+
git \
7+
wget \
8+
libssl-dev \
9+
build-essential \
10+
libffi-dev \
11+
libreadline-dev \
12+
zlib1g-dev \
13+
libsqlite3-dev \
14+
libssl-dev \
15+
&& rm -rf /var/lib/apt/lists/*
16+
17+
WORKDIR /tmp/
18+
19+
# ============================================
20+
ARG PYTHON_VERSION="3.11.1"
21+
ARG PYTHON_SRC="https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz"
22+
23+
RUN wget $PYTHON_SRC \
24+
&& tar -xvzf "Python-${PYTHON_VERSION}.tgz" \
25+
&& cd "Python-${PYTHON_VERSION}" \
26+
&& ./configure \
27+
&& make -j 4 \
28+
&& make install \
29+
&& cd .. \
30+
&& rm "Python-${PYTHON_VERSION}.tgz" \
31+
&& rm -rf "Python-${PYTHON_VERSION}"
32+
33+
34+
# ============================================
35+
ARG PYTHON_VERSION="3.10.9"
36+
ARG PYTHON_SRC="https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz"
37+
38+
RUN wget $PYTHON_SRC \
39+
&& tar -xvzf "Python-${PYTHON_VERSION}.tgz" \
40+
&& cd "Python-${PYTHON_VERSION}" \
41+
&& ./configure \
42+
&& make -j 4 \
43+
&& make install \
44+
&& cd .. \
45+
&& rm "Python-${PYTHON_VERSION}.tgz" \
46+
&& rm -rf "Python-${PYTHON_VERSION}"
47+
48+
# ============================================
49+
ARG PYTHON_VERSION="3.9.16"
50+
ARG PYTHON_SRC="https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz"
51+
52+
RUN wget $PYTHON_SRC \
53+
&& tar -xvzf "Python-${PYTHON_VERSION}.tgz" \
54+
&& cd "Python-${PYTHON_VERSION}" \
55+
&& ./configure \
56+
&& make -j 4 \
57+
&& make install \
58+
&& cd .. \
59+
&& rm "Python-${PYTHON_VERSION}.tgz" \
60+
&& rm -rf "Python-${PYTHON_VERSION}"
61+
62+
# ============================================
63+
ARG PYTHON_VERSION="3.8.16"
64+
ARG PYTHON_SRC="https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz"
65+
66+
RUN wget $PYTHON_SRC \
67+
&& tar -xvzf "Python-${PYTHON_VERSION}.tgz" \
68+
&& cd "Python-${PYTHON_VERSION}" \
69+
&& ./configure \
70+
&& make -j 4 \
71+
&& make install \
72+
&& cd .. \
73+
&& rm "Python-${PYTHON_VERSION}.tgz" \
74+
&& rm -rf "Python-${PYTHON_VERSION}"
75+
76+
# ============================================
77+
78+
ARG PYTHON_VERSION="3.7.17"
79+
ARG PYTHON_SRC="https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz"
80+
81+
RUN wget $PYTHON_SRC \
82+
&& tar -xvzf "Python-${PYTHON_VERSION}.tgz" \
83+
&& cd "Python-${PYTHON_VERSION}" \
84+
&& ./configure \
85+
&& make -j 4 \
86+
&& make install \
87+
&& cd .. \
88+
&& rm "Python-${PYTHON_VERSION}.tgz" \
89+
&& rm -rf "Python-${PYTHON_VERSION}"

Jenkinsfile

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
pipeline {
2+
agent {
3+
label 'docker'
4+
}
5+
stages {
6+
stage ('Docker') {
7+
agent {
8+
dockerfile {
9+
args '-e HOME=/tmp -e BUILD_CONTEXT=ci'
10+
additionalBuildArgs '--target build-tests'
11+
reuseNode true
12+
}
13+
}
14+
stages {
15+
stage('Testing'){
16+
parallel{
17+
18+
stage ('Python 3.11') {
19+
steps {
20+
sh '''
21+
python3.11 -m venv venv-3.11
22+
VENV_DIR=venv-3.11 scripts/with-venv.sh scripts/check-python-version.sh 3.11
23+
VENV_DIR=venv-3.11 COVERAGE_SUFFIX=3.11 scripts/with-venv.sh scripts/runtests.sh
24+
'''
25+
}
26+
}
27+
stage ('Python 3.10') {
28+
steps {
29+
sh '''
30+
python3.10 -m venv venv-3.10
31+
VENV_DIR=venv-3.10 scripts/with-venv.sh scripts/check-python-version.sh 3.10
32+
VENV_DIR=venv-3.10 COVERAGE_SUFFIX=3.10 scripts/with-venv.sh scripts/runtests.sh
33+
'''
34+
}
35+
}
36+
stage ('Python 3.9') {
37+
steps {
38+
sh '''
39+
python3.9 -m venv venv-3.9
40+
VENV_DIR=venv-3.9 scripts/with-venv.sh scripts/check-python-version.sh 3.9
41+
VENV_DIR=venv-3.9 COVERAGE_SUFFIX=3.9 scripts/with-venv.sh scripts/runtests.sh
42+
'''
43+
}
44+
}
45+
stage ('Python 3.8') {
46+
steps {
47+
sh '''
48+
python3.8 -m venv venv-3.8
49+
VENV_DIR=venv-3.8 scripts/with-venv.sh scripts/check-python-version.sh 3.8
50+
VENV_DIR=venv-3.8 COVERAGE_SUFFIX=3.8 scripts/with-venv.sh scripts/runtests.sh
51+
'''
52+
}
53+
}
54+
stage ('Python 3.7') {
55+
steps {
56+
sh '''
57+
python3.7 -m venv venv-3.7
58+
VENV_DIR=venv-3.7 scripts/with-venv.sh scripts/check-python-version.sh 3.7
59+
VENV_DIR=venv-3.7 COVERAGE_SUFFIX=3.7 scripts/with-venv.sh scripts/runtests.sh
60+
'''
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}

scripts/activate-venv.sh

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
PROJECT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. >/dev/null 2>&1 && pwd -P )"
5+
PY_MODULE_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/dev/null 2>&1 && pwd -P )"
6+
7+
VENV_DIR="${VENV_DIR:-venv}"
8+
VENV_ROOT="${VENV_DIR:-$PROJECT_ROOT/$VENV_DIR}"
9+
10+
log() { echo -e "\x1B[92m[OK]\x1B[39m $@"; }
11+
12+
[ ! -d "$VENV_ROOT" ] \
13+
&& log "Missing venv. Creating..." \
14+
&& python3 -m venv "$VENV_ROOT"
15+
16+
source "$VENV_ROOT/bin/activate"
17+
18+
if ! pip3 show wheel 2>&1 >/dev/null; then
19+
log "Installing wheel..."
20+
pip3 install wheel
21+
log "Upgrading pip..."
22+
pip3 install --upgrade pip
23+
log "Upgrading setuptools..."
24+
pip3 install --upgrade setuptools
25+
fi
26+
27+
MODULE_FEATURE="[dev]"
28+
if ! [[ -z "${BUILD_CONTEXT+x}" ]]; then
29+
if [[ "$BUILD_CONTEXT" == "ci" ]]; then
30+
MODULE_FEATURE="[test]" # Will cause testing tools to be installed.
31+
fi
32+
fi
33+
34+
if ! diff "$PY_MODULE_ROOT/setup.py" "$VENV_ROOT/cache/setup.py" 2>&1 >/dev/null; then
35+
log "Install inside venv"
36+
pip3 install -e "${PY_MODULE_ROOT}${MODULE_FEATURE}"
37+
mkdir -p "$VENV_ROOT/cache/"
38+
cp "$PY_MODULE_ROOT/setup.py" "$VENV_ROOT/cache/setup.py"
39+
fi
40+
41+
set +e

scripts/check-python-version.sh

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
shopt -s nocasematch
5+
6+
if [ $# -eq 0 ]; then
7+
echo "Python version must be specified"
8+
exit 1
9+
fi
10+
11+
REQUIRED_VERSION=$1
12+
ACTUAL_PYTHON_VERSION=$(python3 --version)
13+
ACTUAL_PIP_VERSION=$(pip3 --version)
14+
15+
if [[ $ACTUAL_PYTHON_VERSION != *"python ${REQUIRED_VERSION}"* ]]; then
16+
echo "ERROR - Reported python3 version is ${ACTUAL_PYTHON_VERSION}. Expecting Python ${REQUIRED_VERSION}"
17+
exit 1
18+
fi
19+
20+
if [[ $ACTUAL_PIP_VERSION != *"python ${REQUIRED_VERSION}"* ]]; then
21+
echo "ERROR - Reported pip3 version is ${ACTUAL_PIP_VERSION}. Expecting pip for Python ${REQUIRED_VERSION}"
22+
exit 1
23+
fi
24+
25+
echo "Python version OK."
26+
echo " - ${ACTUAL_PYTHON_VERSION}"
27+
echo " - ${ACTUAL_PIP_VERSION}"
28+
exit 0

scripts/runtests.sh

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
COVERAGE_SUFFIX="${COVERAGE_SUFFIX:-dev}"
5+
HTML_COVDIR="htmlcov_${COVERAGE_SUFFIX}"
6+
COV_DATAFILE=".coverage_${COVERAGE_SUFFIX}"
7+
8+
if ! [[ -z "${BUILD_CONTEXT+x}" ]]; then
9+
if [[ "$BUILD_CONTEXT" == "ci" ]]; then
10+
if ! [[ -z "${NODE_NAME+x}" ]]; then
11+
echo "Running tests on agent: ${NODE_NAME}"
12+
fi
13+
fi
14+
fi
15+
16+
set -x
17+
18+
python3 -m coverage run --data-file ${COV_DATAFILE} -m unittest -v
19+
python3 -m mypy udsoncan
20+
python3 -m coverage report --data-file ${COV_DATAFILE}
21+
python3 -m coverage html --data-file ${COV_DATAFILE} -d $HTML_COVDIR
22+

scripts/with-venv.sh

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
PROJECT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. >/dev/null 2>&1 && pwd -P )"
5+
source "$PROJECT_ROOT/scripts/activate-venv.sh"
6+
7+
set -e # activate-venv sets +e
8+
exec "$@"

setup.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@
99

1010
setup(
1111
name='udsoncan',
12-
packages=find_packages(exclude=['test']),
12+
packages=find_packages(exclude=["test", "test.*"], include=['udsoncan', "udsoncan.*"]),
1313
package_data={
1414
'': ['*.conf'],
1515
},
16+
extras_require={
17+
'test': ['mypy', 'coverage'],
18+
'dev': ['mypy', 'ipdb', 'autopep8', 'coverage']
19+
},
1620
version='1.22.1',
1721
description='Implementation of the Unified Diagnostic Service (UDS) protocol (ISO-14229) used in the automotive industry.',
1822
long_description=long_description,

test/ThreadableTest.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from test.UdsTest import UdsTest
22
import threading
33
import queue
4-
import _thread as thread
54

6-
#Class borrowed from Python Socket test suite.
5+
# Class borrowed from Python Socket test suite.
6+
7+
78
class ThreadableTest(UdsTest):
89
def __init__(self, *args, **kwargs):
910
UdsTest.__init__(self, *args, **kwargs)
@@ -26,9 +27,10 @@ def _setUp(self):
2627
# Do some munging to start the client test.
2728
methodname = self.id()
2829
i = methodname.rfind('.')
29-
methodname = methodname[i+1:]
30+
methodname = methodname[i + 1:]
3031
test_method = getattr(self, '_' + methodname)
31-
self.client_thread = thread.start_new_thread(self.clientRun, (test_method,))
32+
self.client_thread = threading.Thread(target=self.clientRun, args=(test_method,))
33+
self.client_thread.start()
3234

3335
try:
3436
self.__setUp()
@@ -81,5 +83,5 @@ def _clientTearDown(self):
8183
self.clientTearDown()
8284
except BaseException as e:
8385
self.queue.put(e)
84-
finally:
85-
thread.exit()
86+
finally:
87+
raise SystemExit()

udsoncan/client.py

+2-9
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,14 @@
1414

1515
from udsoncan.exceptions import *
1616
from udsoncan.configs import default_client_config
17-
from udsoncan.typing import ClientConfig
17+
from udsoncan.typing import ClientConfig, TypedDict
1818
import logging
1919
import binascii
2020
import functools
2121
import time
22-
import sys
2322

2423
from typing import Callable, Optional, Union, Dict, List, Any, cast, Type
2524

26-
if sys.version_info < (3, 8):
27-
class TypedDict:
28-
pass
29-
else:
30-
from typing import TypedDict
31-
3225

3326
class SessionTiming(TypedDict):
3427
p2_server_max: Optional[float]
@@ -118,7 +111,7 @@ def __init__(self, conn: BaseConnection, config: ClientConfig = default_client_c
118111
self.payload_override = Client.PayloadOverrider()
119112
self.last_response = None
120113

121-
self.session_timing = dict(p2_server_max=None, p2_star_server_max=None)
114+
self.session_timing = cast(SessionTiming, dict(p2_server_max=None, p2_star_server_max=None)) # python 3.7 cast
122115

123116
self.refresh_config()
124117

udsoncan/common/DidCodec.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class AsciiCodec(DidCodec):
5858
def __init__(self, string_len: int):
5959
self.string_len = string_len
6060

61-
def encode(self, string_ascii: Any) -> bytes:
61+
def encode(self, string_ascii: Any) -> bytes: # type: ignore
6262
if not isinstance(string_ascii, str):
6363
raise ValueError("AsciiCodec requires a string for encoding")
6464

udsoncan/configs.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from udsoncan.typing import ClientConfig
22
from udsoncan import latest_standard
3+
from typing import cast
34

4-
default_client_config: ClientConfig = {
5+
default_client_config: ClientConfig = cast(ClientConfig, {
56
'exception_on_negative_response': True,
67
'exception_on_invalid_response': True,
78
'exception_on_unexpected_response': True,
@@ -20,4 +21,4 @@
2021
'standard_version': latest_standard, # 2006, 2013, 2020
2122
'use_server_timing': True,
2223
'extended_data_size': None
23-
}
24+
})

udsoncan/tools.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from udsoncan.exceptions import ConfigError
22
from udsoncan.BaseService import BaseService
33

4-
from udsoncan.typing import IOConfigEntry, IOConfig
4+
from udsoncan.typing import IOConfigEntry, IOConfig, CodecDefinition
55

66
from typing import Any, Union, List, Dict, cast
77

@@ -23,9 +23,9 @@ def fetch_io_entry_from_config(did: int, ioconfig: IOConfig) -> IOConfigEntry:
2323
selected = ioconfig[did]
2424

2525
if not isinstance(selected, dict):
26-
selected = {
26+
selected = cast(IOConfigEntry, {
2727
'codec': selected
28-
}
28+
})
2929
return selected
3030

3131

0 commit comments

Comments
 (0)