Skip to content

Refactoring#106

Merged
MusicalNinjaDad merged 50 commits into
mainfrom
refactor
Nov 12, 2025
Merged

Refactoring#106
MusicalNinjaDad merged 50 commits into
mainfrom
refactor

Conversation

@MusicalNinjaDad

@MusicalNinjaDad MusicalNinjaDad commented Nov 12, 2025

Copy link
Copy Markdown
Owner

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:

  • Generalize test case generation by replacing nested loops in TestCases with a combinatorial testOptions approach and a new TestLoop function
  • Centralize test metadata in a TestData map with filter and sorting helpers (AllElfs, noSubDirs, allFiles, allFilesBaseDirOnly)
  • Introduce testOption and testOptions types along with combine and appendif utilities to build test permutations
  • Simplify flag handling in cmd/snagle by using Cobra BoolFunc callbacks to collect snaggle.Option values

Tests:

  • Replace TestCases with TestLoop across tests and update test logic to use testify/assert
  • Remove obsolete TestDetails struct and defaultTests slice in favor of TestData-driven definitions

@sourcery-ai

sourcery-ai Bot commented Nov 12, 2025

Copy link
Copy Markdown

Reviewer's Guide

This 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.go

sequenceDiagram
    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...)
Loading

Class diagram for refactored test framework types

classDiagram
    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
Loading

Class diagram for new test data organization and helpers

classDiagram
    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
Loading

Class diagram for refactored test iterator and output generation

classDiagram
    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
Loading

File-Level Changes

Change Details Files
Overhaul test runner and output generation
  • Replaced TestCases with TestLoop and a generic runTest loop
  • Introduced testOption/testOptions types and combine logic for flag permutations
  • Refactored generateOutput signature to accept testOptions and variadic bins
internal/testing/testiterator.go
Consolidate and expose test fixtures
  • Removed defaultTests and GoodElfs, moved all fixtures into TestData map
  • Added filterTests with helper functions (AllElfs, noSubDirs, etc.)
  • Defined unified TestDetails struct with Elf metadata
internal/testing/testdata.go
Streamline CLI flag registration
  • Removed individual bool flag variables
  • Switched to Cobra Flags.BoolFunc to append snaggle.Option values
  • Aggregated options into a single slice passed to Snaggle call
cmd/snaggle/main.go
Update existing tests and clean up helpers
  • Updated tests in snaggle_test.go, cli_test.go, elf_test.go to use TestLoop and TestData
  • Removed deprecated logging.LinkMessage and adjusted imports (reflect, testify/assert)
  • Expanded testpaths.go for new test data entries
snaggle_test.go
cmd/snaggle/cli_test.go
elf/elf_test.go
internal/logging.go
internal/testpaths.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread internal/testing/testiterator.go
Comment thread cmd/snaggle/main.go
@codecov

codecov Bot commented Nov 12, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.54930% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.47%. Comparing base (4f2ce43) to head (32c3e70).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
internal/testing/testiterator.go 93.22% 6 Missing and 2 partials ⚠️
cmd/snaggle/main.go 55.55% 3 Missing and 1 partial ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MusicalNinjaDad MusicalNinjaDad merged commit d0c162e into main Nov 12, 2025
16 checks passed
@MusicalNinjaDad MusicalNinjaDad deleted the refactor branch November 12, 2025 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant