Skip to content

Commit 8a100f3

Browse files
committed
Add bash wrapper
1 parent 11eed52 commit 8a100f3

5 files changed

Lines changed: 111 additions & 31 deletions

File tree

Jenkinsfile

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ void createCluster(String CLUSTER_SUFFIX) {
1616
--preemptible \
1717
--zone=${region} \
1818
--machine-type='n1-standard-4' \
19-
--cluster-version='1.31' \
19+
--cluster-version='1.32' \
2020
--num-nodes=3 \
2121
--labels='delete-cluster-after-hours=6' \
2222
--disk-size=30 \
@@ -101,6 +101,16 @@ void pushLogFile(String FILE_NAME) {
101101
}
102102
}
103103

104+
void pushReportFile() {
105+
echo "Push final_report.html to S3!"
106+
withCredentials([aws(credentialsId: 'AMI/OVF', accessKeyVariable: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY')]) {
107+
sh """
108+
S3_PATH=s3://percona-jenkins-artifactory-public/\$JOB_NAME/\$(git rev-parse --short HEAD)
109+
aws s3 cp --content-type text/html --quiet final_report.html \$S3_PATH/final_report.html || :
110+
"""
111+
}
112+
}
113+
104114
void pushArtifactFile(String FILE_NAME) {
105115
echo "Push $FILE_NAME file to S3!"
106116

@@ -146,24 +156,6 @@ void markPassedTests() {
146156
}
147157
}
148158

149-
void printKubernetesStatus(String LOCATION, String CLUSTER_SUFFIX) {
150-
sh """
151-
export KUBECONFIG=/tmp/${CLUSTER_NAME}-${CLUSTER_SUFFIX}
152-
echo "========== KUBERNETES STATUS $LOCATION TEST =========="
153-
gcloud container clusters list|grep -E "NAME|${CLUSTER_NAME}-${CLUSTER_SUFFIX} "
154-
echo
155-
kubectl get nodes
156-
echo
157-
kubectl top nodes
158-
echo
159-
kubectl get pods --all-namespaces
160-
echo
161-
kubectl top pod --all-namespaces
162-
echo
163-
kubectl get events --field-selector type!=Normal --all-namespaces --sort-by=".lastTimestamp"
164-
echo "======================================================"
165-
"""
166-
}
167159

168160
String formatTime(def time) {
169161
if (!time || time == "N/A") return "N/A"
@@ -258,15 +250,24 @@ void runTest(Integer TEST_ID) {
258250
export DEBUG_TESTS=1
259251
fi
260252
export KUBECONFIG=/tmp/${CLUSTER_NAME}-${clusterSuffix}
261-
time ./e2e-tests/$testName/run
253+
export PATH="\$HOME/.local/bin:\$PATH"
254+
mkdir -p e2e-tests/reports
255+
256+
REPORT_OPTS="--html=e2e-tests/reports/${testName}.html --junitxml=e2e-tests/reports/${testName}.xml"
257+
258+
# Run native pytest if test_*.py exists, otherwise run bash via wrapper
259+
if ls e2e-tests/$testName/test_*.py 1>/dev/null 2>&1; then
260+
time uv run pytest e2e-tests/$testName/ \$REPORT_OPTS
261+
else
262+
time uv run pytest e2e-tests/test_pytest_wrapper.py --test-name=$testName \$REPORT_OPTS
263+
fi
262264
"""
263265
}
264266
pushArtifactFile("${env.GIT_BRANCH}-${env.GIT_SHORT_COMMIT}-$testName")
265267
tests[TEST_ID]["result"] = "passed"
266268
return true
267269
}
268270
catch (exc) {
269-
printKubernetesStatus("AFTER","$clusterSuffix")
270271
echo "Test $testName has failed!"
271272
if (retryCount >= 1 || currentBuild.nextBuild != null) {
272273
currentBuild.result = 'FAILURE'
@@ -290,7 +291,7 @@ void prepareNode() {
290291
sudo curl -sLo /usr/local/bin/kubectl https://dl.k8s.io/release/\$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl && sudo chmod +x /usr/local/bin/kubectl
291292
kubectl version --client --output=yaml
292293
293-
curl -fsSL https://get.helm.sh/helm-v3.19.0-linux-amd64.tar.gz | sudo tar -C /usr/local/bin --strip-components 1 -xzf - linux-amd64/helm
294+
curl -fsSL https://get.helm.sh/helm-v3.20.0-linux-amd64.tar.gz | sudo tar -C /usr/local/bin --strip-components 1 -xzf - linux-amd64/helm
294295
295296
sudo curl -fsSL https://github.com/mikefarah/yq/releases/download/v4.48.1/yq_linux_amd64 -o /usr/local/bin/yq && sudo chmod +x /usr/local/bin/yq
296297
sudo curl -fsSL https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux64 -o /usr/local/bin/jq && sudo chmod +x /usr/local/bin/jq
@@ -307,6 +308,10 @@ EOF
307308
sudo yum install -y google-cloud-cli google-cloud-cli-gke-gcloud-auth-plugin
308309
309310
curl -sL https://github.com/mitchellh/golicense/releases/latest/download/golicense_0.2.0_linux_x86_64.tar.gz | sudo tar -C /usr/local/bin -xzf - golicense
311+
312+
curl -LsSf https://astral.sh/uv/install.sh | sh
313+
export PATH="\$HOME/.local/bin:\$PATH"
314+
uv sync --locked
310315
"""
311316
installAzureCLI()
312317
azureAuth()
@@ -423,10 +428,10 @@ pipeline {
423428
CLOUDSDK_CORE_DISABLE_PROMPTS = 1
424429
CLEAN_NAMESPACE = 1
425430
OPERATOR_NS = 'psmdb-operator'
426-
GIT_SHORT_COMMIT = sh(script: 'git rev-parse --short HEAD', , returnStdout: true).trim()
431+
GIT_SHORT_COMMIT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
427432
VERSION = "${env.GIT_BRANCH}-${env.GIT_SHORT_COMMIT}"
428-
CLUSTER_NAME = sh(script: "echo jen-psmdb-${env.CHANGE_ID}-${GIT_SHORT_COMMIT}-${env.BUILD_NUMBER} | tr '[:upper:]' '[:lower:]'", , returnStdout: true).trim()
429-
AUTHOR_NAME = sh(script: "echo ${CHANGE_AUTHOR_EMAIL} | awk -F'@' '{print \$1}'", , returnStdout: true).trim()
433+
CLUSTER_NAME = sh(script: "echo jen-psmdb-${env.CHANGE_ID}-${GIT_SHORT_COMMIT}-${env.BUILD_NUMBER} | tr '[:upper:]' '[:lower:]'", returnStdout: true).trim()
434+
AUTHOR_NAME = sh(script: "echo ${CHANGE_AUTHOR_EMAIL} | awk -F'@' '{print \$1}'", returnStdout: true).trim()
430435
ENABLE_LOGGING = "true"
431436
}
432437
agent {
@@ -458,7 +463,7 @@ pipeline {
458463
prepareNode()
459464
script {
460465
if (AUTHOR_NAME == 'null') {
461-
AUTHOR_NAME = sh(script: "git show -s --pretty=%ae | awk -F'@' '{print \$1}'", , returnStdout: true).trim()
466+
AUTHOR_NAME = sh(script: "git show -s --pretty=%ae | awk -F'@' '{print \$1}'", returnStdout: true).trim()
462467
}
463468
for (comment in pullRequest.comments) {
464469
println("Author: ${comment.user}, Comment: ${comment.body}")
@@ -675,12 +680,24 @@ pipeline {
675680
}
676681
}
677682
makeReport()
678-
junit testResults: '*.xml', healthScaleFactor: 1.0
679-
archiveArtifacts '*.xml'
683+
684+
if (fileExists('e2e-tests/reports')) {
685+
sh """
686+
export PATH="\$HOME/.local/bin:\$PATH"
687+
uv run pytest_html_merger -i e2e-tests/reports -o final_report.html
688+
uv run junitparser merge --glob 'e2e-tests/reports/*.xml' final_report.xml
689+
"""
690+
junit testResults: 'final_report.xml', healthScaleFactor: 1.0
691+
archiveArtifacts 'final_report.xml, final_report.html'
692+
pushReportFile()
693+
} else {
694+
junit testResults: '*.xml', healthScaleFactor: 1.0
695+
archiveArtifacts '*.xml'
696+
}
680697

681698
unstash 'IMAGE'
682699
def IMAGE = sh(returnStdout: true, script: "cat results/docker/TAG").trim()
683-
TestsReport = TestsReport + "\r\n\r\ncommit: ${env.CHANGE_URL}/commits/${env.GIT_COMMIT}\r\nimage: `${IMAGE}`\r\n"
700+
TestsReport = TestsReport + "\r\n\r\nCommit: ${env.CHANGE_URL}/commits/${env.GIT_COMMIT}\r\nImage: `${IMAGE}`\r\nTest report: [report](${testUrlPrefix}/${env.GIT_BRANCH}/${env.GIT_SHORT_COMMIT}/final_report.html)\r\n"
684701
pullRequest.comment(TestsReport)
685702
}
686703
deleteOldClusters("$CLUSTER_NAME")

e2e-tests/conftest.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@
3636
_current_namespace: str | None = None
3737

3838

39+
def pytest_addoption(parser: pytest.Parser) -> None:
40+
parser.addoption("--test-name", action="store", default=None, help="Bash test name to run")
41+
42+
43+
@pytest.hookimpl(tryfirst=True)
44+
def pytest_runtest_setup(item: pytest.Item) -> None:
45+
"""Print newline after pytest's verbose test name output."""
46+
print()
47+
48+
3949
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
4050
def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo) -> None:
4151
"""Collect K8s resources when a test fails."""
@@ -62,7 +72,7 @@ def setup_env_vars() -> None:
6272
"KUBE_VERSION": kube_version,
6373
"EKS": "1" if "eks" in git_version else "0",
6474
"GKE": "1" if "gke" in git_version else "0",
65-
"OPENSHIFT": "1" if tools.is_openshift() else "0",
75+
"OPENSHIFT": "1" if tools.is_openshift() else "",
6676
"MINIKUBE": "1" if tools.is_minikube() else "0",
6777
"API": "psmdb.percona.com/v1",
6878
"GIT_COMMIT": tools.get_git_commit(),

e2e-tests/lib/bash_wrapper.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import os
2+
import subprocess
3+
from pathlib import Path
4+
5+
import pytest
6+
7+
8+
def pytest_addoption(parser: pytest.Parser) -> None:
9+
parser.addoption("--test-name", action="store", help="Name of the bash test to run")
10+
11+
12+
def run_bash_test(test_name: str) -> None:
13+
"""Run bash script with live output and capture for error reporting"""
14+
script_path = Path(__file__).parent.parent / test_name / "run"
15+
16+
if not script_path.exists():
17+
pytest.fail(f"Script not found: {script_path}")
18+
19+
original_cwd = os.getcwd()
20+
script_dir = script_path.parent
21+
22+
try:
23+
os.chdir(script_dir)
24+
process = subprocess.Popen(
25+
["bash", "run"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
26+
)
27+
28+
output = []
29+
if process.stdout is not None:
30+
for line in iter(process.stdout.readline, ""):
31+
print(line, end="")
32+
output.append(line)
33+
34+
process.wait()
35+
36+
if process.returncode != 0:
37+
error_msg = f"Test {test_name} failed with exit code {process.returncode}\n\nOUTPUT:\n{''.join(output)}"
38+
pytest.fail(error_msg)
39+
40+
finally:
41+
os.chdir(original_cwd)

e2e-tests/test_pytest_wrapper.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import pytest
2+
from lib.bash_wrapper import run_bash_test
3+
4+
5+
def test_bash_wrapper(request: pytest.FixtureRequest) -> None:
6+
"""Run a bash test script via pytest wrapper."""
7+
test_name = request.config.getoption("--test-name")
8+
if not test_name:
9+
pytest.skip("No --test-name provided")
10+
run_bash_test(test_name)

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ description = "Tests for PSMDB Operator"
55
requires-python = ">=3.13"
66
dependencies = [
77
"deepdiff>=8.5.0",
8+
"junitparser>=3.2.0",
89
"mypy>=1.16.0",
910
"pytest>=8.4.0",
1011
"pytest-dependency>=0.6.0",
1112
"pytest-html>=4.1.1",
13+
"pytest-html-merger>=0.1.0",
1214
"pytest-json-report>=1.5.0",
1315
"pyyaml>=6.0.2",
1416
"rich>=14.0.0",
@@ -17,7 +19,7 @@ dependencies = [
1719
]
1820

1921
[tool.pytest.ini_options]
20-
addopts = "-s --html=e2e-tests/reports/report.html --self-contained-html --json-report --json-report-file=e2e-tests/reports/report.json --junitxml=e2e-tests/reports/report.xml"
22+
addopts = "-v -s --self-contained-html"
2123
render_collapsed = "all"
2224

2325
[[tool.mypy.overrides]]

0 commit comments

Comments
 (0)