Skip to content

Commit c5989a9

Browse files
committed
ci(performance): Add performance tests to CI
1 parent cf44890 commit c5989a9

24 files changed

+176
-20
lines changed

.github/scripts/tests_build.sh

+25-7
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22

33
USAGE="
44
USAGE:
5-
${0} -c <chunk_build_opts>
6-
Example: ${0} -c -t esp32 -i 0 -m 15
5+
${0} -c -type <test_type> <chunk_build_opts>
6+
Example: ${0} -c -type validation -t esp32 -i 0 -m 15
77
${0} -s sketch_name <build_opts>
88
Example: ${0} -s hello_world -t esp32
99
${0} -clean
1010
Remove build and test generated files
1111
"
1212

1313
function clean(){
14-
rm -rf tests/*/build*/
1514
rm -rf tests/.pytest_cache
16-
rm -rf tests/*/__pycache__/
17-
rm -rf tests/*/*.xml
15+
find tests/ -type d -name 'build*' -exec rm -rf "{}" \+
16+
find tests/ -type d -name '__pycache__' -exec rm -rf "{}" \+
17+
find tests/ -name '*.xml' -exec rm -rf "{}" \+
18+
find tests/ -name 'result_*.json' -exec rm -rf "{}" \+
1819
}
1920

2021
SCRIPTS_DIR="./.github/scripts"
@@ -35,6 +36,10 @@ while [ ! -z "$1" ]; do
3536
echo "$USAGE"
3637
exit 0
3738
;;
39+
-type )
40+
shift
41+
test_type=$1
42+
;;
3843
-clean )
3944
clean
4045
exit 0
@@ -52,12 +57,25 @@ source ${SCRIPTS_DIR}/install-arduino-core-esp32.sh
5257

5358
args="-ai $ARDUINO_IDE_PATH -au $ARDUINO_USR_PATH"
5459

60+
if [[ $test_type == "all" ]] || [[ -z $test_type ]]; then
61+
if [ -n "$sketch" ]; then
62+
tmp_sketch_path=$(find tests -name $sketch.ino)
63+
test_type=$(basename $(dirname $(dirname "$tmp_sketch_path")))
64+
echo "Sketch $sketch test type: $test_type"
65+
test_folder="$PWD/tests/$test_type"
66+
else
67+
test_folder="$PWD/tests"
68+
fi
69+
else
70+
test_folder="$PWD/tests/$test_type"
71+
fi
72+
5573
if [ $chunk_build -eq 1 ]; then
5674
BUILD_CMD="${SCRIPTS_DIR}/sketch_utils.sh chunk_build"
57-
args+=" -p $PWD/tests"
75+
args+=" -p $test_folder"
5876
else
5977
BUILD_CMD="${SCRIPTS_DIR}/sketch_utils.sh build"
60-
args+=" -s $PWD/tests/$sketch"
78+
args+=" -s $test_folder/$sketch"
6179
fi
6280

6381
${BUILD_CMD} ${args} $*

.github/scripts/tests_run.sh

+28-6
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ function run_test() {
1515
fi
1616

1717
if [ $len -eq 1 ]; then
18-
# build_dir="tests/$sketchname/build"
18+
# build_dir="$sketchdir/build"
1919
build_dir="$HOME/.arduino/tests/$sketchname/build.tmp"
20-
report_file="tests/$sketchname/$sketchname.xml"
20+
report_file="$sketchdir/$sketchname.xml"
2121
fi
2222

2323
for i in `seq 0 $(($len - 1))`
@@ -28,9 +28,9 @@ function run_test() {
2828
fi
2929

3030
if [ $len -ne 1 ]; then
31-
# build_dir="tests/$sketchname/build$i"
31+
# build_dir="$sketchdir/build$i"
3232
build_dir="$HOME/.arduino/tests/$sketchname/build$i.tmp"
33-
report_file="tests/$sketchname/$sketchname$i.xml"
33+
report_file="$sketchdir/$sketchname$i.xml"
3434
fi
3535

3636
pytest tests --build-dir $build_dir -k test_$sketchname --junit-xml=$report_file
@@ -79,6 +79,10 @@ while [ ! -z "$1" ]; do
7979
echo "$USAGE"
8080
exit 0
8181
;;
82+
-type )
83+
shift
84+
test_type=$1
85+
;;
8286
* )
8387
break
8488
;;
@@ -88,8 +92,26 @@ done
8892

8993
source ${SCRIPTS_DIR}/install-arduino-ide.sh
9094

95+
# If sketch is provided and test type is not, test type is inferred from the sketch path
96+
if [[ $test_type == "all" ]] || [[ -z $test_type ]]; then
97+
if [ -n "$sketch" ]; then
98+
tmp_sketch_path=$(find tests -name $sketch.ino)
99+
test_type=$(basename $(dirname $(dirname "$tmp_sketch_path")))
100+
echo "Sketch $sketch test type: $test_type"
101+
test_folder="$PWD/tests/$test_type"
102+
else
103+
test_folder="$PWD/tests"
104+
fi
105+
else
106+
test_folder="$PWD/tests/$test_type"
107+
fi
108+
91109
if [ $chunk_run -eq 0 ]; then
92-
run_test $target $PWD/tests/$sketch/$sketch.ino $options $erase
110+
if [ -z $sketch ]; then
111+
echo "ERROR: Sketch name is required for single test run"
112+
return 1
113+
fi
114+
run_test $target $test_folder/$sketch/$sketch.ino $options $erase
93115
else
94116
if [ "$chunk_max" -le 0 ]; then
95117
echo "ERROR: Chunks count must be positive number"
@@ -102,7 +124,7 @@ else
102124
fi
103125

104126
set +e
105-
${COUNT_SKETCHES} $PWD/tests $target
127+
${COUNT_SKETCHES} $test_folder $target
106128
sketchcount=$?
107129
set -e
108130
sketches=$(cat sketches.txt)

.github/workflows/hil.yml

+27-7
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ jobs:
1818
gen_chunks:
1919
if: |
2020
contains(github.event.pull_request.labels.*.name, 'hil_test') ||
21+
contains(github.event.pull_request.labels.*.name, 'perf_test') ||
2122
(github.event_name == 'schedule' && github.repository == 'espressif/arduino-esp32')
2223
name: Generate Chunks matrix
2324
runs-on: ubuntu-latest
2425
outputs:
2526
chunks: ${{ steps.gen-chunks.outputs.chunks }}
27+
test_folder: ${{ steps.gen-chunks.outputs.test_folder }}
28+
test_type: ${{ steps.gen-chunks.outputs.test_type }}
2629
steps:
2730
- name: Checkout Repository
2831
uses: actions/checkout@v4
@@ -31,15 +34,29 @@ jobs:
3134
id: gen-chunks
3235
run: |
3336
set +e
34-
.github/scripts/sketch_utils.sh count tests
37+
if [ "${{contains(github.event.pull_request.labels.*.name, 'hil_test')}}" == "true" ] && \
38+
[ "${{contains(github.event.pull_request.labels.*.name, 'perf_test')}}" == "false" ]; then
39+
test_folder="tests/validation"
40+
test_type="validation"
41+
elif [ "${{contains(github.event.pull_request.labels.*.name, 'hil_test')}}" == "false" ] && \
42+
[ "${{contains(github.event.pull_request.labels.*.name, 'perf_test')}}" == "true" ]; then
43+
test_folder="tests/performance"
44+
test_type="performance"
45+
else
46+
test_folder="tests"
47+
test_type="all"
48+
fi
49+
.github/scripts/sketch_utils.sh count $test_folder
3550
sketches=$?
3651
if [[ $sketches -ge ${{env.MAX_CHUNKS}} ]]; then
3752
$sketches=${{env.MAX_CHUNKS}}
3853
fi
3954
set -e
4055
rm sketches.txt
4156
CHUNKS=$(jq -c -n '$ARGS.positional' --args `seq 0 1 $((sketches - 1))`)
42-
echo "chunks=${CHUNKS}" >>$GITHUB_OUTPUT
57+
echo "chunks=${CHUNKS}" >> $GITHUB_OUTPUT
58+
echo "test_folder=${test_folder}" >> $GITHUB_OUTPUT
59+
echo "test_type=${test_type}" >> $GITHUB_OUTPUT
4360
4461
Build:
4562
needs: gen_chunks
@@ -54,14 +71,14 @@ jobs:
5471
uses: actions/checkout@v4
5572
- name: Build sketches
5673
run: |
57-
bash .github/scripts/tests_build.sh -c -t ${{matrix.chip}} -i ${{matrix.chunks}} -m ${{env.MAX_CHUNKS}}
74+
bash .github/scripts/tests_build.sh -c -type ${{ needs.gen_chunks.outputs.test_type }} -t ${{matrix.chip}} -i ${{matrix.chunks}} -m ${{env.MAX_CHUNKS}}
5875
- name: Upload ${{matrix.chip}}-${{matrix.chunks}} artifacts
5976
uses: actions/upload-artifact@v4
6077
with:
6178
name: ${{matrix.chip}}-${{matrix.chunks}}.artifacts
6279
path: |
63-
~/.arduino/tests/*/build*.tmp/*.bin
64-
~/.arduino/tests/*/build*.tmp/*.json
80+
~/.arduino/tests/**/build*.tmp/*.bin
81+
~/.arduino/tests/**/build*.tmp/*.json
6582
if-no-files-found: error
6683
Test:
6784
needs: [gen_chunks, Build]
@@ -94,19 +111,22 @@ jobs:
94111
95112
- name: Run Tests
96113
run: |
97-
bash .github/scripts/tests_run.sh -c -t ${{matrix.chip}} -i ${{matrix.chunks}} -m ${{env.MAX_CHUNKS}} -e
114+
bash .github/scripts/tests_run.sh -c -type ${{ needs.gen_chunks.outputs.test_type }} -t ${{matrix.chip}} -i ${{matrix.chunks}} -m ${{env.MAX_CHUNKS}} -e
98115
99116
- name: Upload test result artifacts
100117
uses: actions/upload-artifact@v4
101118
if: always()
102119
with:
103120
name: test_results-${{matrix.chip}}-${{matrix.chunks}}
104-
path: tests/*/*.xml
121+
path: |
122+
tests/**/*.xml
123+
tests/**/result_*.json
105124
106125
event_file:
107126
name: "Event File"
108127
if: |
109128
contains(github.event.pull_request.labels.*.name, 'hil_test') ||
129+
contains(github.event.pull_request.labels.*.name, 'perf_test') ||
110130
github.event_name == 'schedule'
111131
needs: Test
112132
runs-on: ubuntu-latest

tests/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
build*/
22
__pycache__/
33
*.xml
4+
result_*.json
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <Arduino.h>
2+
3+
#define N_RUNS 3 // Number of runs to average
4+
5+
// Fibonacci number to calculate. Keep between 35 and 45.
6+
#define FIB_N 40
7+
8+
uint32_t times[N_RUNS];
9+
10+
uint64_t fib(uint32_t n) {
11+
if (n < 2) return n;
12+
return fib(n - 1) + fib(n - 2);
13+
}
14+
15+
void setup(){
16+
uint64_t fibonacci;
17+
18+
Serial.begin(115200);
19+
while (!Serial) delay(10);
20+
21+
log_d("Starting fibonacci calculation");
22+
Serial.printf("Runs: %d\n", N_RUNS);
23+
for (int i = 0; i < N_RUNS; i++) {
24+
log_d("Run %d", i);
25+
unsigned long start = millis();
26+
fibonacci = fib(FIB_N);
27+
unsigned long end = millis();
28+
times[i] = end - start;
29+
log_d("Run %d: %lu.%03lu s\n", i, times[i]/1000, times[i]%1000);
30+
}
31+
32+
Serial.printf("N: %d\n", FIB_N);
33+
Serial.printf("Fibonacci(N): %llu\n", fibonacci);
34+
uint32_t sum = 0;
35+
for (int i = 0; i < N_RUNS; i++) {
36+
sum += times[i];
37+
}
38+
uint32_t avg = sum / N_RUNS;
39+
Serial.printf("Average time: %lu.%03lu s\n", avg/1000, avg%1000);
40+
}
41+
42+
void loop(){
43+
vTaskDelete(NULL);
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import json
2+
import logging
3+
import os
4+
5+
LOGGER = logging.getLogger(__name__)
6+
7+
# Fibonacci results starting from fib(35) to fib(45)
8+
fib_results = [9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733]
9+
10+
def test_fibonacci(dut, request):
11+
# Match "Runs: %d"
12+
res = dut.expect(r"Runs: (\d+)", timeout=60)
13+
runs = int(res.group(0).decode("utf-8").split(" ")[1])
14+
LOGGER.info("Number of runs: {}".format(runs))
15+
16+
# Match "N: %d"
17+
res = dut.expect(r"N: (\d+)", timeout=300)
18+
fib_n = int(res.group(0).decode("utf-8").split(" ")[1])
19+
LOGGER.info("Calculating Fibonacci({})".format(fib_n))
20+
21+
# Match "Fibonacci(N): %llu"
22+
res = dut.expect(r"Fibonacci\(N\): (\d+)", timeout=300)
23+
fib_result = int(res.group(0).decode("utf-8").split(" ")[1])
24+
LOGGER.info("Fibonacci({}) = {}".format(fib_n, fib_result))
25+
26+
# Check if the result is correct
27+
assert fib_result == fib_results[fib_n - 35]
28+
29+
# Match "Average time: %lu.%03lu s"
30+
res = dut.expect(r"Average time: (\d+)\.(\d+) s", timeout=300)
31+
avg_time = float(res.group(0).decode("utf-8").split(" ")[2])
32+
LOGGER.info("Average time on {} runs: {} s".format(runs, avg_time))
33+
34+
# Create JSON with results and write it to file
35+
# Always create a JSON with the format (so it can be merged later on):
36+
# { TEST_NAME_STR: TEST_RESULTS_DICT }
37+
results = {
38+
"fibonacci": {
39+
"runs": runs,
40+
"fib_n": fib_n,
41+
"fib_result": fib_result,
42+
"avg_time": avg_time
43+
}
44+
}
45+
46+
current_folder = os.path.dirname(request.path)
47+
with open(current_folder + "/result_fibonacci.json", "w") as f:
48+
try:
49+
f.write(json.dumps(results))
50+
except Exception as e:
51+
LOGGER.warning("Failed to write results to file: {}".format(e))
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)