Skip to content

Commit

Permalink
Add back replace-type (#898)
Browse files Browse the repository at this point in the history
* Add back `replace-types`

Port the `replace-type` parameter into mockery v3. This changes the config schema used in the v2 replace-type parameter to be more grokable. This is also a practical matter: the somewhat complex parsing mechanism in v2 is replaced by simple struct attributes.

The implementation here is quite different from v2. The templating system in v3 requires type information for all types, and thus we cannot do simple string replacements. We have to call `packages.Load` on the referenced type so we can get real type information. This means that `replace-type` will incur additional latency. This latency penalty, however, grants us additional correctness of implementation: the mock templates will have full type information, and if something about the `replace-type` parameter is wrong (either package path or type name don't exist), mockery will explicitly log this as an error.

This does _not_ implement replacements of type constraints as done in v2: #750. This work still needs to be done.

Per #864

* Additional documentation

* Add docs and hint in log message on how to enable force-file-write.
  • Loading branch information
LandonTClipp authored Jan 20, 2025
1 parent 85c4718 commit e307bc6
Show file tree
Hide file tree
Showing 35 changed files with 650 additions and 173 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: documentation
on:
push:
branches: [ v3 ]
branches: [ master, v3 ]
permissions:
contents: write
jobs:
Expand All @@ -28,4 +28,4 @@ jobs:
- name: Deploy docs
run: "mike deploy --push --update-aliases $(grep VERSION mockery-tools.env | cut -d'=' -f 2 | cut -d'.' -f1-2) v3"
env:
GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }}
GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }}
2 changes: 1 addition & 1 deletion .github/workflows/reusable-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
go-version: ${{ matrix.go_vers }}

- name: Download dependencies
run: go mod download
run: go mod download -x

- name: Test
run: go run github.com/go-task/task/v3/cmd/task test.ci
9 changes: 4 additions & 5 deletions .github/workflows/tag-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ on:
permissions:
contents: write
jobs:
# TODO: temporarily disable testing during alpha development
#test:
# uses: ./.github/workflows/reusable-testing.yml
test:
uses: ./.github/workflows/reusable-testing.yml
tag:
runs-on: ubuntu-latest
#needs: test
needs: test
outputs:
tag_result: ${{ steps.tag.outputs.tag_result }}
requested_version: ${{ steps.tag.outputs.requested_version }}
Expand Down Expand Up @@ -96,4 +95,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ secrets.GORELEASER_HOMEBREW_TAP_TOKEN }}
GORELEASER_CURRENT_TAG: ${{ needs.tag.outputs.requested_version }}
#GORELEASER_PREVIOUS_TAG: ${{ needs.tag.outputs.previous_version }}
#GORELEASER_PREVIOUS_TAG: ${{ needs.tag.outputs.previous_version }}
2 changes: 1 addition & 1 deletion .github/workflows/testing-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ jobs:
test:
uses: ./.github/workflows/reusable-testing.yml
with:
ref: ${{ inputs.ref }}
ref: ${{ inputs.ref }}
10 changes: 2 additions & 8 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@ name: Go Test

on:
pull_request:
branches: [v3]
branches: [ master, v3 ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: ./.github/workflows/reusable-testing.yml

uses: ./.github/workflows/reusable-testing.yml
14 changes: 14 additions & 0 deletions .mockery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ template-data:
boilerplate-file: "./.boilerplate.txt"
packages:
github.com/vektra/mockery/v3/internal/fixtures/buildtag/comment:
github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type:
config:
recursive: True
interfaces:
RType:
configs:
- {}
- mockname: RTypeReplaced1
replace-type:
github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt1:
RType1:
pkg-path: github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt2
type-name: RType2

github.com/vektra/mockery/v3/internal/fixtures:
interfaces:
RequesterVariadic:
Expand Down
8 changes: 4 additions & 4 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ tasks:
sources:
- "**/*.go"
cmds:
- go fmt ./...
- gofumpt -l -w .

mocks:
desc: generate new mocks from scratch
Expand Down Expand Up @@ -83,11 +83,11 @@ tasks:
- ./e2e/run_all.sh

test.ci:
deps: [fmt, lint]
deps: [lint]
cmds:
- task: mocks.remove
- task: mocks.generate
- task: mocks
- task: test
- task: mocks.remove
- task: test.e2e

default:
Expand Down
15 changes: 15 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Parameter Descriptions
| `exclude-subpkg-regex` | :fontawesome-solid-x: | `#!yaml []` | A list of regular expressions that denote which subpackages should be excluded when `#!yaml recursive: true` |
| `exclude-regex` | :fontawesome-solid-x: | `#!yaml ""` | When set along with `include-regex`, then interfaces which match `include-regex` but also match `exclude-regex` will not be generated. If `all` is set, or if `include-regex` is not set, then `exclude-regex` has no effect. |
| `filename` | :fontawesome-solid-check: | `#!yaml "mock_{{.InterfaceName}}.go"` | The name of the file the mock will reside in. |
| `force-file-write` | :fontawesome-solid-x: | `#!yaml false` | When set to `#!yaml force-file-write: true`, mockery will forcibly overwrite any existing files. |
| `formatter` | :fontawesome-solid-x: | `#!yaml "goimports"` | The formatter to use on the rendered template. Choices are: `gofmt`, `goimports`, `noop`. |
| `include-regex` | :fontawesome-solid-x: | `#!yaml ""` | When set, only interface names that match the expression will be generated. This setting is ignored if `all: True` is specified in the configuration. To further refine the interfaces generated, use `exclude-regex`. |
| `log-level` | :fontawesome-solid-x: | `#!yaml "info"` | Set the level of the logger |
Expand All @@ -65,11 +66,25 @@ Parameter Descriptions
| [`packages`](features.md#packages-configuration) | :fontawesome-solid-x: | `#!yaml null` | A dictionary containing configuration describing the packages and interfaces to generate mocks for. |
| `pkgname` | :fontawesome-solid-check: | `#!yaml "{{.SrcPackageName}}" | The `#!go package name` given to the generated mock files. |
| [`recursive`](features.md#recursive-package-discovery) | :fontawesome-solid-x: | `#!yaml false` | When set to `true` on a particular package, mockery will recursively search for all sub-packages and inject those packages into the config map. |
| [`replace-type`](replace-type.md) | :fontawesome-solid-x: | `#!yaml {}` | Use this parameter to specify type replacements. |
| `tags` | :fontawesome-solid-x: | `#!yaml ""` | A space-separated list of additional build tags to load packages. |
| `template` | :fontawesome-solid-x: | `#!yaml ""` | The template to use. The choices are `moq`, `mockery`, or a file path provided by `file://path/to/file.txt`. |
| `template-data` | :fontawesome-solid-x: | `#!yaml {}` | A `map[string]any` that provides arbitrary options to the template. Each template will have a different set of accepted keys. Refer to each template's documentation for more details. |


Config Templates
----------------

Parameters marked as being templated have access to a number of template variables and functions.

### Variables

The variables provided are specified in the [`ConfigData`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#ConfigData) struct.

### Functions

All of the functions defined in [`StringManipulationFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#pkg-variables) are available to templated parameters.

Merging Precedence
------------------

Expand Down
57 changes: 57 additions & 0 deletions docs/replace-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: replace-type
---

## Description

The `#!yaml replace-type:` parameter allows you to replace a type in the generated mocks with another type. Take for example the following interface:


```go title="interface.go"
package replace_type

import (
"github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt1"
"github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt2"
)

type RType interface {
Replace1(f rt1.RType1)
}
```

You can selectively replace the `rt1.RType1` with a new type if so desired. For example:

```yaml title=".mockery.yml"
replace-type:
github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt1:
RType1:
pkg-path: github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt2
type-name: RType2
```
The mock will now replace all instances of `rt1.RType1` with `rt2.RType2`. You can see the before and after of `mockery`-style mocks:

=== "before"

```go
// Replace2 provides a mock function for the type RTypeReplaced1
func (_mock *RTypeReplaced1) Replace1(f rt1.RType1) {
_mock.Called(f)
return
}
```

=== "after"

```go
// Replace2 provides a mock function for the type RTypeReplaced1
func (_mock *RTypeReplaced1) Replace1(f rt2.RType2) {
_mock.Called(f)
return
}
```

## Background

This parameter is useful if you need to need to work around packages that use internal types. Take for example the situation found [here](https://github.com/vektra/mockery/issues/864#issuecomment-2567788637), noted by [RangelReale](https://github.com/RangelReale).
1 change: 1 addition & 0 deletions e2e/test_infinite_mocking.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

# New mocks may legimitately be created, so we run mockery once first
num_files_before=$(find . -type f | wc -l)
export MOCKERY_FORCE_FILE_WRITE="true"
go run github.com/go-task/task/v3/cmd/task mocks.generate
num_files_after=$(find . -type f | wc -l)

Expand Down
2 changes: 1 addition & 1 deletion e2e/test_mockery_generation.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

go run github.com/go-task/task/v3/cmd/task mocks.generate
go run github.com/go-task/task/v3/cmd/task mocks
rt=$?
if [ $rt -ne 0 ]; then
echo "ERROR: non-zero return code from mockery"
Expand Down
8 changes: 0 additions & 8 deletions e2e/test_recursive_package_with_only_autogenerated_files.sh

This file was deleted.

27 changes: 13 additions & 14 deletions internal/cmd/mockery.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ import (
pkg "github.com/vektra/mockery/v3/internal"
"github.com/vektra/mockery/v3/internal/logging"
"github.com/vektra/mockery/v3/internal/stackerr"
"github.com/vektra/mockery/v3/template"
"golang.org/x/tools/go/packages"
)

var (
ErrCfgFileNotFound = errors.New("config file not found")
)
var ErrCfgFileNotFound = errors.New("config file not found")

func NewRootCmd() (*cobra.Command, error) {
var pFlags *pflag.FlagSet
Expand Down Expand Up @@ -81,18 +80,18 @@ func Execute() {
if err != nil {
os.Exit(1)
}
if cmd.Execute(); err != nil {
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}

type RootApp struct {
Config pkg.RootConfig
Config template.RootConfig
}

func GetRootApp(ctx context.Context, flags *pflag.FlagSet) (*RootApp, error) {
r := &RootApp{}
config, _, err := pkg.NewRootConfig(ctx, flags)
config, _, err := template.NewRootConfig(ctx, flags)
if err != nil {
return nil, stackerr.NewStackErrf(err, "failed to get config")
}
Expand All @@ -113,7 +112,7 @@ type InterfaceCollection struct {
outFilePath *pathlib.Path
srcPkg *packages.Package
outPkgName string
interfaces []*pkg.Interface
interfaces []*template.Interface
template string
}

Expand All @@ -122,19 +121,19 @@ func NewInterfaceCollection(
outFilePath *pathlib.Path,
srcPkg *packages.Package,
outPkgName string,
template string,
templ string,
) *InterfaceCollection {
return &InterfaceCollection{
srcPkgPath: srcPkgPath,
outFilePath: outFilePath,
srcPkg: srcPkg,
outPkgName: outPkgName,
interfaces: make([]*pkg.Interface, 0),
template: template,
interfaces: make([]*template.Interface, 0),
template: templ,
}
}

func (i *InterfaceCollection) Append(ctx context.Context, iface *pkg.Interface) error {
func (i *InterfaceCollection) Append(ctx context.Context, iface *template.Interface) error {
collectionFilepath := i.outFilePath.String()
interfaceFilepath := iface.Config.FilePath().String()
log := zerolog.Ctx(ctx).With().
Expand Down Expand Up @@ -258,7 +257,7 @@ func (r *RootApp) Run() error {
}
if err := mockFileToInterfaces[filePath.String()].Append(
ctx,
pkg.NewInterface(
template.NewInterface(
iface.Name,
iface.FileName,
iface.File,
Expand Down Expand Up @@ -313,8 +312,8 @@ func (r *RootApp) Run() error {
fileLog.Err(err).Msg("can't determine if outfile exists")
return fmt.Errorf("determining if outfile exists: %w", err)
}
if outFileExists {
fileLog.Error().Msg("output file exists, can't write mocks")
if outFileExists && !packageConfig.Config.ForceFileWrite {
fileLog.Error().Bool("force-file-write", packageConfig.Config.ForceFileWrite).Msg("output file exists, can't write mocks")
return fmt.Errorf("outfile exists")
}

Expand Down
9 changes: 6 additions & 3 deletions internal/cmd/showconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"github.com/knadh/koanf/providers/structs"
"github.com/knadh/koanf/v2"
"github.com/spf13/cobra"
pkg "github.com/vektra/mockery/v3/internal"
"github.com/vektra/mockery/v3/internal/logging"
"github.com/vektra/mockery/v3/template"
)

func NewShowConfigCmd() *cobra.Command {
Expand All @@ -24,13 +24,16 @@ func NewShowConfigCmd() *cobra.Command {
}

ctx := log.WithContext(context.Background())
conf, _, err := pkg.NewRootConfig(ctx, cmd.Parent().PersistentFlags())
conf, _, err := template.NewRootConfig(ctx, cmd.Parent().PersistentFlags())
if err != nil {
return err
}

k := koanf.New("|")
k.Load(structs.Provider(conf, "koanf"), nil)
if err := k.Load(structs.Provider(conf, "koanf"), nil); err != nil {
log.Err(err).Msg("failed to load config")
return err
}
b, _ := k.Marshal(koanfYAML.Parser())
fmt.Println(string(b))

Expand Down
2 changes: 1 addition & 1 deletion internal/errors.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pkg
package internal

import "fmt"

Expand Down
Loading

0 comments on commit e307bc6

Please sign in to comment.