Streamline tests with a custom iterator#88
Conversation
Reviewer's GuideThis PR overhauls the test infrastructure by introducing a custom TestCases iterator alongside a centralized Asserter helper, refactors existing table-driven tests to consume that iterator, removes legacy testing helpers, and cleans up comparison utilities. A new iter dependency is added to orchestrate subtests more declaratively, streamlining repetitive loops. Class diagram for new and updated test helpersclassDiagram
class Asserter {
- Testify *assert.Assertions
- t *testing.T
+ DirectoryContents(ExpectedContents map[string]string, dir string)
+ LinkedFile(path1 string, path2 string)
+ Stdout(expected []string, actual []string, mustBeLinked ...string)
}
class TestDetails {
+ Name string
+ Path string
+ Bin binaryDetails
+ SnagTo string
+ SnagAs string
}
class TestCase {
+ Src string
+ Dest string
+ ExpectedStdout []string
+ ExpectedFiles map[string]string
+ ExpectedBinary binaryDetails
+ Options []snaggle.Option
+ Flags []string
}
class binaryDetails {
+ Description string
+ Elf elf.Elf
+ Dynamic bool
+ Exe bool
+ Lib bool
+ HasInterpreter bool
}
Asserter --> assert.Assertions
Asserter --> testing.T
TestDetails --> binaryDetails
TestCase --> binaryDetails
TestCase --> snaggle.Option
Class diagram for refactored comparison utilitiesclassDiagram
class SameInode {
+ SameInode(path1 string, path2 string) (bool, error)
}
class SameFile {
+ SameFile(path1 string, path2 string) bool
}
SameFile --> SameInode
Flow diagram for the new TestCases iterator usageflowchart TD
A["TestCases(t, tests...)"] --> B["Iterate over inplace: false/true"]
B --> C["Iterate over each TestDetails"]
C --> D["t.Run(desc, func(t *testing.T) {...})"]
D --> E["Create TestCase"]
E --> F["generateOutput(bin, &tc, inplace)"]
F --> G["testbody(t, tc)"]
B --> H["If not specific tests, run directory tests (recursive/non-recursive)"]
H --> I["t.Run(desc, func(t *testing.T) {...})"]
I --> J["Create TestCase for directory"]
J --> K["For each bin, generateOutput"]
K --> L["testbody(t, tc)"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #88 +/- ##
==========================================
- Coverage 74.56% 74.36% -0.20%
==========================================
Files 11 12 +1
Lines 578 593 +15
==========================================
+ Hits 431 441 +10
- Misses 104 108 +4
- Partials 43 44 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `internal/testing/testdata.go:21` </location>
<code_context>
}
-var commonElfs = map[string]elf.Elf{
+var GoodElfs = map[string]binaryDetails{
"hello_pie": {
- Name: "hello_pie",
</code_context>
<issue_to_address>
**issue (complexity):** Consider introducing a helper function to automatically set binaryDetails flags based on the Elf struct, reducing manual repetition.
You can pull the repeated flag‐logic into a single helper and then just pass your Elf literal plus a description. For example:
```go
func newBinaryDetails(desc string, e elf.Elf) binaryDetails {
return binaryDetails{
Description: desc,
Elf: e,
Dynamic: e.Type == elf.Type(elf.PIE) || e.Type == elf.Type(elf.DYN),
Exe: e.Type == elf.Type(elf.PIE) || e.Type == elf.Type(elf.EXE),
Lib: e.Type == elf.Type(elf.DYN),
HasInterpreter: e.Interpreter != "",
}
}
```
Then your map becomes much shorter:
```go
var GoodElfs = map[string]binaryDetails{
"hello_pie": newBinaryDetails("PIE no dependencies", elf.Elf{ Name: "hello_pie", Path: P_hello_pie, Class: elf.EI_CLASS(elf.ELF64), Type: elf.Type(elf.PIE), Interpreter: P_ld_linux, Dependencies: nil }),
"hello_static": newBinaryDetails("Static linked executable", elf.Elf{ Name: "hello_static", Path: P_hello_static, Class: elf.EI_CLASS(elf.ELF64), Type: elf.Type(elf.EXE), Interpreter: "", Dependencies: nil }),
// …and so on for the other entries…
}
var Ldd = newBinaryDetails("Not an ELF", elf.Elf{
Name: "ldd", Path: P_ldd,
Class: elf.EI_CLASS(elf.UNDEF), Type: elf.Type(elf.ELFNONE),
Interpreter: "", Dependencies: nil,
})
```
This preserves exactly the same behavior while removing all of the manual flag repetitions.
</issue_to_address>
### Comment 2
<location> `internal/testing/testiterator.go:84` </location>
<code_context>
+//
+// Just don't try to use SkipNow, FailNow or t.Parallel as Go's rangefunc & t.Run hacks collide and mess up
+// goroutine clarity. There is probably a good fix with channels but I can't be bothered right now...
+func TestCases(t *testing.T, tests ...TestDetails) iter.Seq2[*testing.T, TestCase] {
+ var specficTestsRequested bool
+ if tests == nil {
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring TestCases to return a slice of TestCase and use a simple for loop for subtests.
Here’s one way to collapse all that indirection into a simple slice + single `t.Run(…)` loop without losing any of the test-matrix:
1. Change `TestCases` to build and return `[]TestCase` instead of an `iter.Seq2`:
```go
// returns all permutations of TestCase (in-place, recursive, directory, etc)
func TestCases(t *testing.T, tests ...TestDetails) []TestCase {
base := defaultTests
if len(tests) > 0 {
base = tests
}
var out []TestCase
for _, inplace := range []bool{false, true} {
for _, td := range base {
name := td.Name
if inplace {
name += "_inplace"
}
tc := buildSingleCase(t, td, inplace)
tc.Name = name
out = append(out, tc)
}
// only for default set, also test directory runs
if len(tests) == 0 {
for _, recursive := range []bool{false, true} {
name := "Directory"
if inplace {
name += "_inplace"
}
if recursive {
name += "_recursive"
}
tc := buildDirCase(t, defaultTests, inplace, recursive)
tc.Name = name
out = append(out, tc)
}
}
}
return out
}
```
2. Implement the two helpers to keep things DRY:
```go
func buildSingleCase(t *testing.T, td TestDetails, inplace bool) TestCase {
tc := TestCase{ /* initialize fields */ }
generateOutput(td, &tc, inplace)
return tc
}
func buildDirCase(t *testing.T, bins []TestDetails, inplace, recursive bool) TestCase {
tc := TestCase{ /* dst/src/options based on flags */ }
for _, td := range bins {
generateOutput(td, &tc, inplace)
}
return tc
}
```
3. In your test, consume that slice directly:
```go
func TestSomething(t *testing.T) {
for _, tc := range TestCases(t) {
t.Run(tc.Name, func(t *testing.T) {
Assert := assert.New(t)
testbody(t, tc) // whatever your body does
})
}
}
```
Benefits:
- No custom iterator type or higher-order function needed.
- One flat `for _, tc := range …` loop makes it immediately clear how many subtests you'll get.
- You can still reuse `generateOutput` and keep the same flags/options logic.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Summary by Sourcery
Refactor test infrastructure by introducing a custom iterator and unified Asserter API, migrating all test suites to a table-driven pattern and removing redundant helper functions.
New Features:
Enhancements:
Build: