Skip to content

go generate ./... is broken #7976

Description

@williammartin

Description

When adding moq generate stanzas, I expected to be able to run go generate ./... to create my new mocks. Unfortunately, this fails (on trunk):

➜  cli git:(trunk) go generate ./...
couldn't load source package: /Users/williammartin/workspace/cli/internal/config/stub.go:12:24: undefined: ConfigMock (and 2 more errors)
moq [flags] source-dir interface [interface2 [interface3 [...]]]
  -fmt string
        go pretty-printer: gofmt, goimports or noop (default gofmt)
  -out string
        output file (default stdout)
  -pkg string
        package name (default will infer)
  -rm
        first remove output file, if it exists
  -skip-ensure
        suppress mock implementation check, avoid import cycle if mocks generated outside of the tested package
  -stub
        return zero values when no mock implementation is provided, do not panic
  -version
        show the version for moq
  -with-resets
        generate functions to facilitate resetting calls made to a mock
Specifying an alias for the mock is also supported with the format 'interface:alias'
Ex: moq -pkg different . MyInterface:MyMock
internal/config/config.go:20: running "moq": exit status 1

This results in a broken go project.

This isn't explicitly a bug since it doesn't affect the built artifact of the CLI, but it is a really annoying developer experience.

Activity

  1. added
    tech-debtA chore that addresses technical debt
    coreThis issue is not accepting PRs from outside contributors
    on Sep 11, 2023
  2. williammartin commented on May 7, 2024

    @williammartin
    MemberAuthor

    Why is this happening?

    Well the problem is to do with dependencies and packages. Let's take a look at the Config interface which lives in the config package:

    //go:generate moq -rm -out config_mock.go . Config
    type Config interface {
    GetOrDefault(string, string) (string, error)
    Set(string, string, string)
    Write() error
    Migrate(Migration) error
    CacheDir() string
    Aliases() *AliasConfig
    Authentication() *AuthConfig
    Browser(string) string
    Editor(string) string
    GitProtocol(string) string
    HTTPUnixSocket(string) string
    Pager(string) string
    Prompt(string) string
    Version() string
    }

    This Config interface is defined in the config package. The go generate comment also says to generate the ConfigMock struct in the same package. Finally, there is a stub config used in tests that also exists in the same package that references the concrete ConfigMock.

    With the -rm flag provided to moq via go generate, the config_mock.go file is deleted, then the package is loaded in order to generate the code from the interface. However, because stub.go references ConfigMock and that has been removed, the package is in a broken state.

    However, that's not all because changing the interface and regenerating exhibits the same problem. For example, changing a return type results in the ConfigMock and therefore the Stub from no longer satisfying the interface. Thus, loading the package to regenerate the interface also results in a broken state.

    Typically this isn't a problem because test specific code lives in _test packages but for whatever reason the CLI has mostly been built with tests living inside the implementation packages.

  3. williammartin commented on May 7, 2024

    @williammartin
    MemberAuthor

    What do we do about this?

    At it's core the issue is that our dependency graph is all messed up. The usual thing to do here is to have the interface in a consumer package, put the mock into its own package mocks or foo_test and then to move the stubs as well so that the import graph makes sense. However we can't move the stub easily because it is reaching into the unexported config.cfg struct.

    I think the right thing to do is to then to move the interface to a shared location e.g. a gh package, then generate the mock in a nearby package. In this way the stub can stay in config and have access to the unexported struct.

    The other advantage to this is that it now starts to form a what I would call a "domain package", a place where you can look to see the shapes of the puzzle pieces that are required for the gh app to work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

coreThis issue is not accepting PRs from outside contributorstech-debtA chore that addresses technical debt

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions