Refactoring#106
Conversation
Reviewer's GuideThis PR performs a comprehensive refactoring of the test framework and CLI flag handling. It replaces the ad-hoc TestCases iterator with a generic TestLoop and testOptions combinator, consolidates all fixture data into a unified TestData map with filter helpers, simplifies the generateOutput logic, and streamlines Cobra flag registration to build an options slice. Minor cleanup includes removing legacy helpers, updating imports, and adjusting existing tests to use the new APIs. Sequence diagram for refactored CLI flag handling in main.gosequenceDiagram
participant User as actor User
participant Cobra
participant "main.go"
participant "options[]"
User->>Cobra: Set flag (e.g. --copy)
Cobra->>"main.go": Call addOption(snaggle.Copy())
"main.go"->>"options[]": Append snaggle.Copy() to options
User->>Cobra: Execute command
Cobra->>"main.go": rootCmd.Execute()
"main.go"->>"options[]": Use options slice for snaggle.Snaggle()
"main.go"->>snaggle: Call Snaggle(src, dest, options...)
Class diagram for refactored test framework typesclassDiagram
class TestDetails {
+string Name
+string Path
+string SnagTo
+string SnagAs
+bool InSubdir
+bool Symlink
+bool NonElf
+bool Dynamic
+bool Exe
+bool Lib
+bool HasInterpreter
+elf.Elf Elf
}
class testOption {
+string name
+string negativeSuffix
+snaggle.Option option
+string flag
}
class testOptions {
+[]string names
+[]snaggle.Option options
+[]string flags
+includes(opt testOption) bool
}
class elf.Elf {
+string Name
+string Path
+EI_CLASS Class
+Type Type
+string Interpreter
+[]string Dependencies
}
TestDetails --> elf.Elf
testOptions "1" -- "*" testOption
Class diagram for new test data organization and helpersclassDiagram
class testListing {
+map[string]TestDetails
}
class TestData {
<<variable>>
+testListing
}
class filterTests {
<<function>>
+[]TestDetails filterTests(tests testListing, filterFunc func(TestDetails) bool)
}
class AllElfs {
<<function>>
+[]TestDetails AllElfs()
}
class noSubDirs {
<<function>>
+[]TestDetails noSubDirs()
}
class allFiles {
<<function>>
+[]TestDetails allFiles()
}
class allFilesBaseDirOnly {
<<function>>
+[]TestDetails allFilesBaseDirOnly()
}
TestData --> testListing
filterTests --> testListing
AllElfs --> filterTests
noSubDirs --> filterTests
allFiles --> filterTests
allFilesBaseDirOnly --> filterTests
Class diagram for refactored test iterator and output generationclassDiagram
class TestLoop {
+iter.Seq2[*testing.T, TestCase] TestLoop(t *testing.T, tests ...TestDetails)
}
class TestCase {
+string Src
+string Dest
+[]string ExpectedStdout
+map[string]string ExpectedFiles
+[]snaggle.Option Options
+[]string Flags
}
class generateOutput {
<<function>>
+void generateOutput(tc *TestCase, options testOptions, bins ...TestDetails)
}
TestLoop --> TestCase
generateOutput --> TestCase
generateOutput --> TestDetails
TestLoop --> TestDetails
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- The combine function’s for loop uses 'for mask := range bitmask' on an int which doesn’t produce the intended iterations; switch to 'for mask := 0; mask < 1<<n; mask++' to correctly generate combinations.
- The testOptions.includes check does a linear search on the names slice for every test, which may add overhead; consider using a map or bitmask of enabled options for O(1) lookups.
- The generic appendif uses reflect.ValueOf to skip zero values, introducing reflection overhead; you could avoid this by special-casing the zero value for the element type or leveraging Go generics to eliminate reflection.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The combine function’s for loop uses 'for mask := range bitmask' on an int which doesn’t produce the intended iterations; switch to 'for mask := 0; mask < 1<<n; mask++' to correctly generate combinations.
- The testOptions.includes check does a linear search on the names slice for every test, which may add overhead; consider using a map or bitmask of enabled options for O(1) lookups.
- The generic appendif uses reflect.ValueOf to skip zero values, introducing reflection overhead; you could avoid this by special-casing the zero value for the element type or leveraging Go generics to eliminate reflection.
## Individual Comments
### Comment 1
<location> `internal/testing/testiterator.go:212` </location>
<code_context>
+ flags []string
+}
+
+func combine(opts ...testOption) []testOptions {
+ n := len(opts)
+ options := make([]testOptions, 0, 2^n)
</code_context>
<issue_to_address>
**issue (bug_risk):** Bitmask iteration is incorrect and will not enumerate all combinations.
'bitmask := (1 << n)' creates a single value, so 'for mask := range bitmask' won't work. Use 'for mask := 0; mask < (1 << n); mask++' to correctly iterate over all bitmask combinations.
</issue_to_address>
### Comment 2
<location> `cmd/snaggle/main.go:72` </location>
<code_context>
-)
+var options []snaggle.Option
+
+func addOption(option snaggle.Option) func(string) error {
+ return func(_ string) error {
+ options = append(options, option)
</code_context>
<issue_to_address>
**issue (bug_risk):** Global 'options' slice may accumulate state across multiple runs.
Since 'options' is global and accumulates values, repeated CLI invocations in the same process can result in stale or unexpected options. Reset 'options' at the start of each command to avoid this issue.
</issue_to_address>
### Comment 3
<location> `internal/testing/testdata.go:48` </location>
<code_context>
- Description: "PIE no dependencies",
+type testListing = map[string]TestDetails
+
+var TestData = testListing{
+ P_ctypes_so: {
+ Name: "dyn_lib",
</code_context>
<issue_to_address>
**issue (complexity):** Consider switching the master test listing from a map to a pre-sorted slice to remove the need for conversion and sorting in your filtering helpers.
Here’s one way to keep all of the new filtering APIs but dramatically reduce the boilerplate (no more map↔slice conversion, no runtime sort, no 3rd-party imports):
1. Change your master listing from a `map[string]TestDetails` to a *pre-sorted* `[]TestDetails`.
2. Make `filterTests` operate on a slice only (no sort needed if your literal is in lexical order).
```go
// 1) master list as a slice, laid out in lexical Path order
var TestData = []TestDetails{
// subdir non‐ELF first (P_build_sh < P_ctypes_so < …)
{ Path: P_build_sh, InSubdir: true, NonElf: true },
{ Name: "dyn_lib", Path: P_ctypes_so, /* … */ },
// … continue in lex order of Path …
}
// 2) simplified filterTests – no sort, no strings import
func filterTests(tests []TestDetails, keep func(TestDetails) bool) []TestDetails {
out := make([]TestDetails, 0, len(tests))
for _, t := range tests {
if keep(t) {
out = append(out, t)
}
}
return out
}
// 3) your exported helpers remain the same:
func AllElfs() []TestDetails { return filterTests(TestData, func(td TestDetails) bool { return !td.NonElf }) }
func noSubDirs() []TestDetails { return filterTests(TestData, func(td TestDetails) bool { return !td.InSubdir && !td.NonElf }) }
// … etc …
```
This way you:
- eliminate the map→slice conversion loop
- eliminate the runtime sort + `strings`/`slices` imports
- still keep all of your named helpers (`AllElfs`, `noSubDirs`, …) and current behavior.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #106 +/- ##
==========================================
- Coverage 77.17% 76.47% -0.71%
==========================================
Files 12 13 +1
Lines 828 748 -80
==========================================
- Hits 639 572 -67
+ Misses 130 119 -11
+ Partials 59 57 -2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary by Sourcery
Refactor internal testing framework by consolidating test data, generalizing test iteration via combinatorial option handling, and simplifying CLI flag registration to collect options directly.
Enhancements:
Tests: