Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 68 additions & 50 deletions _demo/embed/test-esp-serial-startup.sh
Original file line number Diff line number Diff line change
@@ -1,44 +1,22 @@
#!/bin/bash
# ESP serial targets smoke test (build + emulator run only).
# ESP serial targets smoke test (emulator run only).
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMP_DIR="$SCRIPT_DIR/.test_tmp_$$"
TEST_GO="$TEMP_DIR/main.go"

ESP32C3_PREFIX="esp32c3_smoke"
ESP32_PREFIX="esp32_smoke"
CASE_ROOT="$SCRIPT_DIR/testdata/esp32-serial"

cleanup() {
rm -rf "$TEMP_DIR"
}
trap cleanup EXIT

build_target() {
local target="$1"
local prefix="$2"
local label="$3"

echo "==> Building for $label target ($target): ELF + BIN..."
llgo build -target="$target" -o "$prefix" -obin "$TEST_GO"

if [ ! -f "${prefix}.elf" ]; then
echo "✗ FAIL: Build failed, ${prefix}.elf not found"
exit 1
fi

if [ ! -f "${prefix}.bin" ]; then
echo "✗ FAIL: Build failed, ${prefix}.bin not found"
exit 1
fi

echo "✓ PASS: $label build artifacts generated"
}

run_emulator_smoke() {
local target="$1"
local label="$2"
local expected="$3"
local case_dir="$3"
local expected_file="$4"

echo ""
echo "=== Smoke: $label emulator output ==="
Expand All @@ -47,7 +25,7 @@ run_emulator_smoke() {
run_out_file=$(mktemp "${TEMP_DIR}/run_${target}.XXXX.log")

set +e
llgo run -a -target="$target" -emulator . 2>&1 | tee "$run_out_file"
llgo run -a -target="$target" -emulator "$case_dir" 2>&1 | tee "$run_out_file"
local run_rc=${PIPESTATUS[0]}
set -e

Expand All @@ -59,46 +37,86 @@ run_emulator_smoke() {
echo "[WARN] $label emulator exited with code $run_rc; validating output tail instead"
fi

local last_line
last_line=$(printf "%s\n" "$run_out" | tr -d '\r' | awk 'NF{line=$0} END{print line}')
local normalized_out
normalized_out=$(printf "%s\n" "$run_out" | tr -d '\r')

if [ "$last_line" = "$expected" ]; then
echo "✓ PASS: $label output ends with $expected"
local expected_tail
expected_tail=$(cat "$expected_file")

local normalized_expected
normalized_expected=$(printf "%s" "$expected_tail" | tr -d '\r')

local n
n=$(printf "%s\n" "$normalized_expected" | awk 'END{print NR}')
if [ -z "$n" ] || [ "$n" -le 0 ]; then
echo "✗ FAIL: invalid expected tail for $label"
exit 1
fi

local actual_tail
actual_tail=$(printf "%s\n" "$normalized_out" | awk 'NF{print}' | tail -n "$n")

if [ "$actual_tail" = "$normalized_expected" ]; then
echo "✓ PASS: $label output tail (last $n line(s)) matched"
else
echo "✗ FAIL: $label output mismatch"
echo "Last line: $last_line"
echo "Expected tail (last $n line(s)):"
printf "%s\n" "$normalized_expected"
echo "Actual tail:"
printf "%s\n" "$actual_tail"
echo ""
echo "Full output:"
echo "$run_out"
exit 1
fi
}

mkdir -p "$TEMP_DIR"
run_case() {
local case_dir="$1"
local case_name
case_name="$(basename "$case_dir")"

echo "==> Creating minimal test program..."
cat > "$TEST_GO" << 'EOGO'
package main

import "github.com/goplus/lib/c"
local expected_file="$case_dir/expect.txt"
if [ ! -f "$case_dir/main.go" ]; then
echo "✗ FAIL: missing testcase source: $case_dir/main.go"
exit 1
fi

func main() {
c.Printf(c.Str("Hello World\n"))
run_emulator_smoke "esp32c3-basic" "ESP32-C3 [$case_name]" "$case_dir" "$expected_file"
run_emulator_smoke "esp32" "ESP32 [$case_name]" "$case_dir" "$expected_file"
}
EOGO

cd "$TEMP_DIR"
run_all_cases() {
local found=0
local case_dir
exec 3< <(find "$CASE_ROOT" -mindepth 1 -maxdepth 1 -type d | sort)
while IFS= read -r case_dir <&3; do
if [ -f "$case_dir/main.go" ] && [ -f "$case_dir/expect.txt" ]; then
found=1
run_case "$case_dir"
fi
done
exec 3<&-

if [ "$found" -eq 0 ]; then
echo "✗ FAIL: no testcase found under $CASE_ROOT (need main.go + expect.txt)"
exit 1
fi
}

echo ""
echo "=== ESP Serial Smoke Tests: Build + Emulator Run ==="
mkdir -p "$TEMP_DIR"
if [ ! -d "$CASE_ROOT" ]; then
echo "✗ FAIL: testcase root not found: $CASE_ROOT"
exit 1
fi

build_target "esp32c3" "$ESP32C3_PREFIX" "ESP32-C3"
run_emulator_smoke "esp32c3-basic" "ESP32-C3" "Hello World"
cd "$SCRIPT_DIR"

build_target "esp32" "$ESP32_PREFIX" "ESP32"
run_emulator_smoke "esp32" "ESP32" "Hello World"
echo ""
echo "=== ESP Serial Smoke Tests: Emulator Run ==="
run_all_cases

echo ""
echo "=== Smoke Tests Passed ==="
echo "✓ ESP32-C3 build + emulator run passed"
echo "✓ ESP32 build + emulator run passed"
echo "✓ ESP32-C3 and ESP32 emulator smoke passed for all serial testcases"
echo "✓ Cases are discovered only from testdata/esp32-serial (main.go + expect.txt)"
1 change: 1 addition & 0 deletions _demo/embed/testdata/esp32-serial/chello/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello World
7 changes: 7 additions & 0 deletions _demo/embed/testdata/esp32-serial/chello/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/goplus/lib/c"

func main() {
c.Printf(c.Str("Hello World\n"))
}
5 changes: 5 additions & 0 deletions _demo/embed/testdata/esp32-serial/float-1685/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
+5.000000e+00 +8.000000e+00
1 +2.000000e+00
0x0 +0.000000e+00 notOk: true
0x0 +0.000000e+00 true
3 +6.280000e+00
60 changes: 60 additions & 0 deletions _demo/embed/testdata/esp32-serial/float-1685/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

type point struct {
x float64
y float64
}

type myPoint = point

func (p *point) scale(factor float64) {
p.x *= factor
p.y *= factor
}

func (p *myPoint) move(dx, dy float64) {
p.x += dx
p.y += dy
}

func pair(f float64) (int, float64) {
return 1, f
}

type bar struct {
pb *byte
f float32
}

type foo struct {
pb *byte
f float32
}

func xadd(a, b int) int {
return a + b
}

func double(v float64) float64 {
return v * 2
}

func main() {
pt := &myPoint{1, 2}
pt.scale(2)
pt.move(3, 4)
println(pt.x, pt.y)

i, f := pair(2.0)
println(i, f)

// Keep this case on the float-format path without triggering
// esp32 type-assert timeout cases tracked separately.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The comment says "without triggering esp32 type-assert timeout cases" but the code uses a plain composite literal — no type assertion is involved. The intent is clear (avoid the struczero-style v.(bar) path), but the phrasing could confuse readers into thinking this code somehow relates to type assertions. Consider rephrasing to make it explicit, e.g.:

// Uses plain composite literal instead of type assertion to avoid
// the esp32 type-assert timeout tracked separately.

ret, ok := bar{}, false
println(ret.pb, ret.f, "notOk:", !ok)

ret2, ok2 := foo{}, true
println(ret2.pb, ret2.f, ok2)

println(xadd(1, 2), double(3.14))
}
101 changes: 94 additions & 7 deletions internal/crosscompile/compile/libc/libc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,8 +558,8 @@ func TestGetNewlibESP32ConfigXtensa(t *testing.T) {
}

// Test Groups configuration
if len(config.Groups) != 3 {
t.Errorf("Expected 2 groups, got %d", len(config.Groups))
if len(config.Groups) != 6 {
t.Errorf("Expected 6 groups, got %d", len(config.Groups))
} else {
// Group 0: libcrt0
group0 := config.Groups[0]
Expand Down Expand Up @@ -631,6 +631,92 @@ func TestGetNewlibESP32ConfigXtensa(t *testing.T) {
}
}

// Group 3: libm sources with -fbuiltin -fno-math-errno.
group3 := config.Groups[3]
expectedOutput3 := "libm-fbuiltin_fno_math_errno-" + target + ".a"
if group3.OutputFileName != expectedOutput3 {
t.Errorf("Group3 OutputFileName expected '%s', got '%s'", expectedOutput3, group3.OutputFileName)
}
for _, sample := range []string{
filepath.Join(baseDir, "newlib", "libm", "common", "s_fpclassify.c"),
filepath.Join(baseDir, "newlib", "libm", "common", "sf_fpclassify.c"),
} {
found := false
for _, file := range group3.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group3 files", sample)
}
}
if !slices.Contains(group3.CFlags, "-fbuiltin") || !slices.Contains(group3.CFlags, "-fno-math-errno") {
t.Errorf("Expected group3 CFlags to contain -fbuiltin and -fno-math-errno")
}

// Group 4: default libm sources.
group4 := config.Groups[4]
expectedOutput4 := "libm-default-" + target + ".a"
if group4.OutputFileName != expectedOutput4 {
t.Errorf("Group4 OutputFileName expected '%s', got '%s'", expectedOutput4, group4.OutputFileName)
}
for _, sample := range []string{
filepath.Join(baseDir, "newlib", "libm", "complex", "cabs.c"),
filepath.Join(baseDir, "newlib", "libm", "math", "e_acos.c"),
} {
found := false
for _, file := range group4.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group4 files", sample)
}
}
if slices.Contains(group4.CFlags, "-fbuiltin") || slices.Contains(group4.CFlags, "-fno-math-errno") || slices.Contains(group4.CFlags, "-D_LIBM") {
t.Errorf("Expected group4 CFlags to not contain -fbuiltin/-fno-math-errno/-D_LIBM")
}

// Group 5: machine/xtensa libm sources built with -D_LIBM.
group5 := config.Groups[5]
expectedOutput5 := "libm-machine_xtensa_d_libm-" + target + ".a"
if group5.OutputFileName != expectedOutput5 {
t.Errorf("Group5 OutputFileName expected '%s', got '%s'", expectedOutput5, group5.OutputFileName)
}
for _, sample := range []string{
filepath.Join(baseDir, "newlib", "libm", "machine", "xtensa", "ef_sqrt.c"),
filepath.Join(baseDir, "newlib", "libm", "machine", "xtensa", "fegetenv.c"),
} {
found := false
for _, file := range group5.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group5 files", sample)
}
}
if !slices.Contains(group5.CFlags, "-D_LIBM") {
t.Errorf("Expected group5 CFlags to contain -D_LIBM")
}
if !slices.Contains(group5.CFlags, "-I"+filepath.Join(baseDir, "newlib", "libc", "machine", "xtensa", "include")) {
t.Errorf("Expected group5 CFlags to contain xtensa machine include path")
}
if !slices.Contains(group5.CFlags, "-I"+filepath.Join(baseDir, "newlib", "libc", "xtensa")) {
t.Errorf("Expected group5 CFlags to contain xtensa sys include path")
}

totalLibmFiles := len(group3.Files) + len(group4.Files) + len(group5.Files)
if totalLibmFiles != 380 {
t.Errorf("Expected 380 xtensa libm files from command-log build list, got %d", totalLibmFiles)
}

// Test LDFlags and CCFlags
if len(group0.LDFlags) == 0 {
t.Error("Expected non-empty LDFlags in group0")
Expand Down Expand Up @@ -698,8 +784,8 @@ func TestGroupConfiguration(t *testing.T) {

t.Run("Xtensa_GroupCount", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa(baseDir, target)
if len(config.Groups) != 3 {
t.Errorf("Expected 2 groups for Xtensa, got %d", len(config.Groups))
if len(config.Groups) != 6 {
t.Errorf("Expected 6 groups for Xtensa, got %d", len(config.Groups))
}
})

Expand All @@ -726,12 +812,13 @@ func TestGroupConfiguration(t *testing.T) {
expectedNames := []string{
"libcrt0-" + target + ".a",
"libgloss-" + target + ".a",
"libc-" + target + ".a",
"libm-fbuiltin_fno_math_errno-" + target + ".a",
"libm-default-" + target + ".a",
"libm-machine_xtensa_d_libm-" + target + ".a",
}

for i, group := range config.Groups {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old guard if i >= len(expectedNames) { return } was removed, but the loop still iterates over config.Groups. If a future change adds a group without updating expectedNames, this will panic with index-out-of-bounds instead of producing a clear test failure.

Consider bounding the loop or adding a length check:

Suggested change
for i, group := range config.Groups {
for i, group := range config.Groups {
if i >= len(expectedNames) {
t.Errorf("unexpected extra group %d: %s", i, group.OutputFileName)
break
}

if i >= len(expectedNames) {
return
}
if group.OutputFileName != expectedNames[i] {
t.Errorf("Group %d expected name '%s', got '%s'", i, expectedNames[i], group.OutputFileName)
}
Expand Down
Loading
Loading