cltest: combine IR and runtime golden checks#1771
cltest: combine IR and runtime golden checks#1771luoliwoshang wants to merge 6 commits intogoplus:mainfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a ModuleHook mechanism to the build configuration, enabling the observation and snapshotting of LLVM modules during the build process. It also adds a new test runner, RunAndTestFromDir, which validates both runtime output and IR snapshots against golden files. The review feedback highlights several performance and maintainability improvements: specifically, making module snapshotting optional to avoid expensive string conversions, using more efficient byte operations when parsing IR files for module IDs, and refactoring the test runners to eliminate code duplication.
cl/cltest/cltest.go
Outdated
| return result.output, nil | ||
| } | ||
|
|
||
| func runWithConf(relPkg, pkgDir string, conf *build.Config, usePackagePattern bool) (*runResult, error) { |
There was a problem hiding this comment.
The runWithConf function now unconditionally stringifies every LLVM module in the build via the ModuleHook. This is an expensive operation that will cause a significant performance regression in the test suite, especially for RunAndCaptureWithConf which is used by many tests that only need execution output. Consider adding a snapshotModules boolean parameter to control this behavior.
References
- Identify performance bottlenecks and optimize for efficiency.
cl/cltest/cltest.go
Outdated
| modules := make(map[string]string) | ||
| prevHook := localConf.ModuleHook | ||
| localConf.ModuleHook = func(pkgPath string, mod gllvm.Module) { | ||
| if prevHook != nil { | ||
| prevHook(pkgPath, mod) | ||
| } | ||
| if _, ok := modules[pkgPath]; !ok { | ||
| modules[pkgPath] = mod.String() | ||
| } | ||
| } |
There was a problem hiding this comment.
| // RunAndTestFromDir executes tests under relDir and validates both runtime | ||
| // output and the pre-transform package IR snapshot when the corresponding | ||
| // golden files exist. | ||
| func RunAndTestFromDir(t *testing.T, sel, relDir string, ignore []string, opts ...RunOption) { |
| lines := strings.Split(string(data), "\n") | ||
| if len(lines) == 0 { | ||
| return "" | ||
| } | ||
| const prefix = "; ModuleID = '" | ||
| line := strings.TrimSpace(lines[0]) | ||
| if !strings.HasPrefix(line, prefix) || !strings.HasSuffix(line, "'") { | ||
| return "" | ||
| } | ||
| return output, nil | ||
| return strings.TrimSuffix(strings.TrimPrefix(line, prefix), "'") |
There was a problem hiding this comment.
strings.Split(string(data), "\n") is inefficient for large IR files as it copies the entire buffer into a string and then creates a slice for every line. Since only the first line is needed to extract the ModuleID, it's better to use bytes operations to avoid unnecessary allocations.
References
- Optimize for efficiency and avoid unnecessary iterations or memory allocations.
cl/cltest/cltest.go
Outdated
| result, err = runWithConf(relPkg, pkgDir, opts.conf, true) | ||
| } else { | ||
| conf := build.NewDefaultConf(build.ModeRun) | ||
| result, err = runWithConf(relPkg, pkgDir, conf, true) | ||
| } | ||
| if err != nil && checkOutput { |
There was a problem hiding this comment.
When checkOutput is false but checkIR is true, a non-nil err from runWithConf is silently ignored and the function proceeds to validate the IR snapshot. If the build partially failed, this could lead to comparing against incomplete/incorrect IR and producing misleading test results. Consider at minimum logging the error, or failing explicitly when the build errors out regardless of which golden files are being checked.
cl/cltest/cltest.go
Outdated
| modules := make(map[string]string) | ||
| prevHook := localConf.ModuleHook | ||
| localConf.ModuleHook = func(pkgPath string, mod gllvm.Module) { | ||
| if prevHook != nil { | ||
| prevHook(pkgPath, mod) | ||
| } | ||
| if _, ok := modules[pkgPath]; !ok { | ||
| modules[pkgPath] = mod.String() | ||
| } | ||
| } |
There was a problem hiding this comment.
mod.String() serializes the entire LLVM module for every package built, even on the RunAndCaptureWithConf path (usePackagePattern=false) where the captured module strings are never used. Consider gating the hook installation on usePackagePattern (or a dedicated flag) so the existing non-IR code path doesn't pay the serialization cost.
| func TestRunAndTestFromTestrt(t *testing.T) { | ||
| var ignore []string | ||
| if runtime.GOOS == "linux" { | ||
| ignore = []string{ | ||
| "./_testrt/asmfull", // Output is macOS-specific. |
There was a problem hiding this comment.
The original TestFromTestrt called cl.SetDebug(cl.DbgFlagAll) before running IR tests, exercising the compiler's debug-logging paths. The merged test no longer enables debug mode, silently dropping that coverage. If the debug-mode testing was intentional, consider preserving it (e.g., via a WithDebug run option or a separate subtest).
|
Good consolidation — merging the separate IR and run test passes into a single build is a solid improvement. The |
338fff5 to
33baf29
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1771 +/- ##
=======================================
Coverage 92.87% 92.87%
=======================================
Files 48 48
Lines 13353 13353
=======================================
Hits 12402 12402
Misses 757 757
Partials 194 194 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
f02c2fd to
e4070a5
Compare
e33ac41 to
b3bc372
Compare
Summary
build.Config.ModuleHookso tests can observe a package module beforeTransformModule, and cover it with a focused unit testclcompiler suites tocltest.RunAndTestFromDir, so cases with bothexpect.txtandout.llcan reuse one run/build path while embed target suites can still opt out of IR checks withWithIRCheck(false)cltestgolden handling by sharing one golden reader, keeping IR-only cases onllgen.GenFrom, and capturingmod.String()only for the target package named by the goldenModuleIDTesting
go test ./internal/build -run TestModuleHookReceivesMainPackageModule -count=1go test ./cl ./ssa ./chore/gentests -run '^$' -count=1go test ./cl -run 'TestRunAndTestFromTestpy|TestRunAndTestFromTestlibc|TestFilterEmulatorOutput' -count=1