Skip to content

Commit e3a6ced

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

24 files changed

+175
-20
lines changed

Diff for: .github/scripts/tests_build.sh

+24-7
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
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*/
14+
rm -rf tests/**/build*/
1515
rm -rf tests/.pytest_cache
16-
rm -rf tests/*/__pycache__/
17-
rm -rf tests/*/*.xml
16+
rm -rf tests/**/__pycache__/
17+
rm -rf tests/**/*.xml
1818
}
1919

2020
SCRIPTS_DIR="./.github/scripts"
@@ -35,6 +35,10 @@ while [ ! -z "$1" ]; do
3535
echo "$USAGE"
3636
exit 0
3737
;;
38+
-type )
39+
shift
40+
test_type=$1
41+
;;
3842
-clean )
3943
clean
4044
exit 0
@@ -52,12 +56,25 @@ source ${SCRIPTS_DIR}/install-arduino-core-esp32.sh
5256

5357
args="-ai $ARDUINO_IDE_PATH -au $ARDUINO_USR_PATH"
5458

59+
if [[ $test_type == "all" ]] || [[ -z $test_type ]]; then
60+
if [ -n "$sketch" ]; then
61+
tmp_sketch_path=$(find tests -name $sketch.ino)
62+
test_type=$(basename $(dirname $(dirname "$tmp_sketch_path")))
63+
echo "Sketch $sketch test type: $test_type"
64+
test_folder="$PWD/tests/$test_type"
65+
else
66+
test_folder="$PWD/tests"
67+
fi
68+
else
69+
test_folder="$PWD/tests/$test_type"
70+
fi
71+
5572
if [ $chunk_build -eq 1 ]; then
5673
BUILD_CMD="${SCRIPTS_DIR}/sketch_utils.sh chunk_build"
57-
args+=" -p $PWD/tests"
74+
args+=" -p $test_folder"
5875
else
5976
BUILD_CMD="${SCRIPTS_DIR}/sketch_utils.sh build"
60-
args+=" -s $PWD/tests/$sketch"
77+
args+=" -s $test_folder/$sketch"
6178
fi
6279

6380
${BUILD_CMD} ${args} $*

Diff for: .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)

Diff for: .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/**/*.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

Diff for: tests/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
build*/
22
__pycache__/
33
*.xml
4+
result_*.json

Diff for: tests/performance/fibonacci/fibonacci.ino

+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+
}

Diff for: tests/performance/fibonacci/test_fibonacci.py

+51
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.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)