├── LICENSE ├── README.md ├── _example ├── simple │ ├── a.go │ ├── a_test.go │ ├── go.mod │ └── mock_test.go └── timesample │ ├── a.go │ ├── go.mod │ ├── go.sum │ └── timesample_test.go ├── go.mod ├── go.sum ├── internal ├── goflags │ ├── build.go │ ├── goflags.go │ ├── gotest.go │ └── testing.go ├── mocknn.go └── overlay │ ├── gen.go │ ├── gen_test.go │ └── testdata │ ├── golden │ ├── TestGenerator_Generate-normal-mock-files.golden │ └── TestGenerator_Generate-normal-overlay-json.golden │ └── src │ └── normal │ ├── a.go │ ├── go.mod │ └── mock_test.go ├── main.go └── version.txt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Takuya Ueda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mocknn 2 | 3 | [![pkg.go.dev][gopkg-badge]][gopkg] 4 | 5 | mocknn is a mocking tool using `-overlay` option of `go test`. 6 | 7 | This is experimental project. APIs may change, do not use your product development. 8 | If you try to use this tool, please give me your feedback by filing an issue. 9 | 10 | ## How to install 11 | 12 | ``` 13 | $ go install github.com/tenntenn/mocknn@latest 14 | ``` 15 | 16 | ## How to use 17 | 18 | For example, there is a .go file which is test target. 19 | 20 | ```go 21 | // simple.go: test target 22 | package simple 23 | 24 | import "fmt" 25 | 26 | const msg string = "hello" 27 | 28 | func New(n int) *T { 29 | return &T{n: n} 30 | } 31 | 32 | type T struct { 33 | n int 34 | } 35 | 36 | func (t *T) V() int { 37 | return t.n 38 | } 39 | 40 | func F(t *T) { 41 | fmt.Println(msg, t.V()) 42 | } 43 | ``` 44 | 45 | simple_test.go is a test file for simple.go. 46 | 47 | ```go 48 | package simple_test 49 | 50 | import ( 51 | "testing" 52 | 53 | "github.com/tenntenn/mocknn/_example/simple" 54 | ) 55 | 56 | func Test(t *testing.T) { 57 | v := simple.New(10) 58 | simple.F(v) 59 | } 60 | ``` 61 | 62 | mock_test.go provides mockings for simple.go. 63 | 64 | ```go 65 | package simple 66 | 67 | //mocknn: msg 68 | const msgMock = "mock" 69 | 70 | //mocknn: T 71 | type MockT struct { 72 | m int 73 | } 74 | 75 | //mocknn: New 76 | func MockNew(m int) *MockT { 77 | return &MockT{m: m * 2} 78 | } 79 | 80 | func (t *MockT) V() int { 81 | return t.m 82 | } 83 | ``` 84 | 85 | mocknn replaces implementation to mocking which has `//mocknn:` comment directive. 86 | mocknn uses `-overlay` option to replace mocking files. 87 | 88 | ``` 89 | # use original implementation 90 | $ go test 91 | hello 10 92 | PASS 93 | ok github.com/tenntenn/mocknn/_example/simple 0.359s 94 | 95 | # use mockings 96 | $ go test -overlay=`mocknn` 97 | mock 20 98 | PASS 99 | ok github.com/tenntenn/mocknn/_example/simple 0.273s 100 | 101 | # same as go test -overlay=`mocknn` 102 | $ mocknn test 103 | mock 20 104 | PASS 105 | ok github.com/tenntenn/mocknn/_example/simple 0.273s 106 | ``` 107 | 108 | mocknn can work with [testtime](https://github.com/tenntenn/testtime). 109 | 110 | ``` 111 | $ cd _example/timesample 112 | $ mocknn test -overlay=`testtime` 113 | 2006-01-02 03:00:00 +0000 UTC 114 | PASS 115 | ok github.com/tenntenn/mocknn/_example/timesample 0.837s 116 | ``` 117 | 118 | ## Examples 119 | 120 | See [_example](./_example) directory. 121 | 122 | 123 | [gopkg]: https://pkg.go.dev/github.com/tenntenn/mocknn 124 | [gopkg-badge]: https://pkg.go.dev/badge/github.com/tenntenn/testtime?status.svg 125 | -------------------------------------------------------------------------------- /_example/simple/a.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | import "fmt" 4 | 5 | const msg string = "hello" 6 | 7 | func New(n int) *T { 8 | return &T{n: n} 9 | } 10 | 11 | type T struct { 12 | n int 13 | } 14 | 15 | func (t *T) V() int { 16 | return t.n 17 | } 18 | 19 | func F(t *T) { 20 | fmt.Println(msg, t.V()) 21 | } 22 | -------------------------------------------------------------------------------- /_example/simple/a_test.go: -------------------------------------------------------------------------------- 1 | package simple_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/tenntenn/mocknn/_example/simple" 7 | ) 8 | 9 | func Test(t *testing.T) { 10 | v := simple.New(10) 11 | simple.F(v) 12 | } 13 | -------------------------------------------------------------------------------- /_example/simple/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/mocknn/_example/simple 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /_example/simple/mock_test.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | //mocknn: msg 4 | const msgMock = "mock" 5 | 6 | //mocknn: T 7 | type MockT struct { 8 | m int 9 | } 10 | 11 | //mocknn: New 12 | func MockNew(m int) *MockT { 13 | return &MockT{m: m * 2} 14 | } 15 | 16 | func (t *MockT) V() int { 17 | return t.m 18 | } 19 | -------------------------------------------------------------------------------- /_example/timesample/a.go: -------------------------------------------------------------------------------- 1 | package timesample 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func F() { 9 | fmt.Println(time.Now()) 10 | } 11 | -------------------------------------------------------------------------------- /_example/timesample/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/mocknn/_example/timesample 2 | 3 | go 1.18 4 | 5 | require github.com/tenntenn/testtime v0.2.2 6 | -------------------------------------------------------------------------------- /_example/timesample/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/testtime v0.2.2 h1:y6K00BUNg7cRE9WpkBX/Bn+WgmV5/a3hsw7xGNyF2p0= 2 | github.com/tenntenn/testtime v0.2.2/go.mod h1:gXZpxnMoBEV+JZwooprQ65lIbR2Kzk5PpP/deHMn+Is= 3 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 4 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 5 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 6 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 7 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 8 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 9 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 10 | -------------------------------------------------------------------------------- /_example/timesample/timesample_test.go: -------------------------------------------------------------------------------- 1 | package timesample 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/tenntenn/testtime" 8 | ) 9 | 10 | func Test(t *testing.T) { 11 | testtime.SetTime(t, parseTime(t, "03:00:00")) 12 | F() 13 | } 14 | 15 | func parseTime(t *testing.T, v string) time.Time { 16 | t.Helper() 17 | tm, err := time.Parse("2006/01/02 15:04:05", "2006/01/02 "+v) 18 | if err != nil { 19 | t.Fatal("unexpected error:", err) 20 | } 21 | return tm 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/mocknn 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/tenntenn/golden v0.2.0 7 | go.uber.org/multierr v1.8.0 8 | golang.org/x/tools v0.1.11 9 | ) 10 | 11 | require ( 12 | github.com/google/go-cmp v0.5.6 // indirect 13 | github.com/josharian/mapfs v0.0.0-20210615234106-095c008854e6 // indirect 14 | github.com/josharian/txtarfs v0.0.0-20210615234325-77aca6df5bca // indirect 15 | go.uber.org/atomic v1.7.0 // indirect 16 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect 17 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 5 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 6 | github.com/josharian/mapfs v0.0.0-20210615234106-095c008854e6 h1:c+ctPFdISggaSNCfU1IueNBAsqetJSvMcpQlT+0OVdY= 7 | github.com/josharian/mapfs v0.0.0-20210615234106-095c008854e6/go.mod h1:Rv/momJI8DgrWnBZip+SgagpcgORIZQE5SERlxNb8LY= 8 | github.com/josharian/txtarfs v0.0.0-20210615234325-77aca6df5bca h1:a8xeK4GsWLE4LYo5VI4u1Cn7ZvT1NtXouXR3DdKLB8Q= 9 | github.com/josharian/txtarfs v0.0.0-20210615234325-77aca6df5bca/go.mod h1:UbC32ft9G/jG+sZI8wLbIBNIrYr7vp/yqMDa9SxVBNA= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 14 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 15 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 16 | github.com/tenntenn/golden v0.2.0 h1:ENbHNS5P2Bcnh2QWQcwtNPDYnIvFGuK4lKVDkCq4AHs= 17 | github.com/tenntenn/golden v0.2.0/go.mod h1:OB8A7xwUZ9xE19KXoOMPl223hhcH4uD8oeQS9fLTiEE= 18 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 19 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 20 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 21 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 22 | go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 24 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 25 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 26 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 27 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= 28 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 29 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 30 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 31 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 32 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= 39 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 41 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 42 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 43 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 44 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 45 | golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= 46 | golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= 47 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 48 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 49 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 50 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 51 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 53 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 54 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 55 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 56 | -------------------------------------------------------------------------------- /internal/goflags/build.go: -------------------------------------------------------------------------------- 1 | package goflags 2 | 3 | import "flag" 4 | 5 | func Build(flags *flag.FlagSet) { 6 | // -ovelay use in mocknn 7 | _ = flags.Bool("a", false, "") 8 | _ = flags.Bool("n", false, "") 9 | _ = flags.Int("p", 0, "") 10 | _ = flags.Bool("race", false, "") 11 | _ = flags.Bool("msan", false, "") 12 | _ = flags.Bool("asan", false, "") 13 | _ = flags.Bool("v", false, "") 14 | _ = flags.Bool("work", false, "") 15 | _ = flags.Bool("x", false, "") 16 | _ = flags.String("asmflags", "", "") 17 | _ = flags.String("buildmode", "", "") 18 | _ = flags.Bool("buildvcs", false, "") 19 | _ = flags.String("compiler", "gc", "") 20 | _ = flags.String("gccgoflags", "", "") 21 | _ = flags.String("gcflags", "", "") 22 | _ = flags.String("installsuffix", "", "") 23 | _ = flags.String("ldflags", "", "") 24 | _ = flags.Bool("linkshared", false, "") 25 | _ = flags.String("mode", "", "") 26 | _ = flags.Bool("modcacherw", false, "") 27 | _ = flags.String("modfile", "", "") 28 | _ = flags.String("pkgdir", "", "") 29 | _ = flags.String("tags", "", "") 30 | _ = flags.Bool("trimpath", false, "") 31 | _ = flags.String("toolexec", "", "") 32 | } 33 | -------------------------------------------------------------------------------- /internal/goflags/goflags.go: -------------------------------------------------------------------------------- 1 | package goflags 2 | 3 | import "flag" 4 | 5 | func All(flags *flag.FlagSet) { 6 | Testing(flags) 7 | Build(flags) 8 | GoTest(flags) 9 | } 10 | -------------------------------------------------------------------------------- /internal/goflags/gotest.go: -------------------------------------------------------------------------------- 1 | package goflags 2 | 3 | import "flag" 4 | 5 | func GoTest(flags *flag.FlagSet) { 6 | _ = flags.Bool("args", false, "") 7 | _ = flags.Bool("c", false, "") 8 | _ = flags.String("exec", "", "") 9 | _ = flags.Bool("i", false, "") 10 | _ = flags.Bool("json", false, "") 11 | _ = flags.String("o", "", "") 12 | } 13 | -------------------------------------------------------------------------------- /internal/goflags/testing.go: -------------------------------------------------------------------------------- 1 | package goflags 2 | 3 | import "flag" 4 | 5 | func Testing(flags *flag.FlagSet) { 6 | // -v is defined in Build 7 | _ = flags.String("bench", "", "") 8 | _ = flags.Bool("benchmem", false, "") 9 | _ = flags.Duration("benchtime", 0, "") 10 | _ = flags.String("blockprofile", "", "") 11 | _ = flags.Int("blockprofilerate", 0, "") 12 | _ = flags.Uint("count", 0, "") 13 | _ = flags.String("coverprofile", "", "") 14 | _ = flags.String("cpu", "", "") 15 | _ = flags.String("cpuprofile", "", "") 16 | _ = flags.Bool("failfast", false, "") 17 | _ = flags.String("fuzz", "", "") 18 | _ = flags.String("fuzzcachedir", "", "") 19 | _ = flags.Duration("fuzzminimizetime", 0, "") 20 | _ = flags.Duration("fuzztime", 0, "") 21 | _ = flags.Bool("fuzzworker", false, "") 22 | _ = flags.String("list", "", "") 23 | _ = flags.String("memprofile", "", "") 24 | _ = flags.Int("memprofilerate", 0, "") 25 | _ = flags.String("mutexprofile", "", "") 26 | _ = flags.Int("mutexprofilefraction", 0, "") 27 | _ = flags.String("outputdir", "", "") 28 | _ = flags.Bool("paniconexit0", false, "") 29 | _ = flags.Int("parallel", 0, "") 30 | _ = flags.String("run", "", "") 31 | _ = flags.Bool("short", false, "") 32 | _ = flags.String("shuffle", "", "") 33 | _ = flags.String("testlogfile", "", "") 34 | _ = flags.Duration("timeout", 0, "") 35 | _ = flags.String("trace", "", "") 36 | } 37 | -------------------------------------------------------------------------------- /internal/mocknn.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | 12 | "github.com/tenntenn/mocknn/internal/goflags" 13 | "github.com/tenntenn/mocknn/internal/overlay" 14 | "go.uber.org/multierr" 15 | "golang.org/x/tools/go/packages" 16 | ) 17 | 18 | const ( 19 | exitSuccess = 0 20 | exitError = 1 21 | ) 22 | 23 | type Mocknn struct { 24 | Version string 25 | Dir string 26 | Output io.Writer 27 | ErrOutput io.Writer 28 | Input io.Reader 29 | } 30 | 31 | func Main(version string, args []string) int { 32 | m := &Mocknn{ 33 | Version: version, 34 | Dir: ".", 35 | Output: os.Stdout, 36 | ErrOutput: os.Stderr, 37 | Input: os.Stdin, 38 | } 39 | return m.Main(args) 40 | } 41 | 42 | func (m *Mocknn) Main(args []string) int { 43 | if err := m.Run(args); err != nil { 44 | fmt.Fprintln(m.ErrOutput, "mocknn:", err) 45 | return exitError 46 | } 47 | return exitSuccess 48 | } 49 | 50 | func (m *Mocknn) Run(args []string) error { 51 | if len(args) == 0 { 52 | args = []string{"."} 53 | } 54 | 55 | switch args[0] { 56 | case "-v": 57 | if _, err := fmt.Fprintln(os.Stdout, "mocknn", m.Version); err != nil { 58 | return err 59 | } 60 | case "test": 61 | if err := m.testWithMock(args[1:]); err != nil { 62 | return err 63 | } 64 | default: 65 | if err := m.printOverlayJSON(args); err != nil { 66 | return err 67 | } 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func (m *Mocknn) load(patterns []string) (_ []*packages.Package, rerr error) { 74 | config := &packages.Config{ 75 | Dir: m.Dir, 76 | Tests: true, 77 | Mode: packages.NeedName | packages.NeedTypes | 78 | packages.NeedSyntax | packages.NeedTypesInfo | 79 | packages.NeedModule, 80 | } 81 | 82 | pkgs, err := packages.Load(config, patterns...) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | packages.Visit(pkgs, nil, func(pkg *packages.Package) { 88 | for _, err := range pkg.Errors { 89 | rerr = multierr.Append(rerr, err) 90 | } 91 | }) 92 | 93 | if rerr != nil { 94 | return nil, rerr 95 | } 96 | 97 | return pkgs, nil 98 | } 99 | 100 | func (m *Mocknn) testWithMock(args []string) (rerr error) { 101 | tmpdir, err := os.MkdirTemp("", "mocknn-*") 102 | if err != nil { 103 | return err 104 | } 105 | defer func() { 106 | rerr = multierr.Append(rerr, os.RemoveAll(tmpdir)) 107 | }() 108 | 109 | var flagOverlay string 110 | 111 | flags := flag.NewFlagSet("mocknn test", flag.ContinueOnError) 112 | flags.SetOutput(io.Discard) 113 | flags.StringVar(&flagOverlay, "overlay", "", "overlay json") 114 | goflags.All(flags) 115 | if err := flags.Parse(args); err != nil { 116 | return err 117 | } 118 | 119 | var initOverlay *packages.OverlayJSON 120 | if flagOverlay != "" { 121 | f, err := os.Open(flagOverlay) 122 | if err != nil { 123 | return err 124 | } 125 | defer f.Close() 126 | 127 | if err := json.NewDecoder(f).Decode(&initOverlay); err != nil { 128 | return err 129 | } 130 | } 131 | 132 | pkgs, err := m.load(flags.Args()) 133 | if err != nil { 134 | return err 135 | } 136 | 137 | g := &overlay.Generator{ 138 | Dir: tmpdir, 139 | Pkgs: pkgs, 140 | Overlay: initOverlay, 141 | } 142 | 143 | overlayJSON, err := g.Generate() 144 | if err != nil { 145 | return err 146 | } 147 | 148 | f, err := os.Create(filepath.Join(tmpdir, "overlay.json")) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | if err := json.NewEncoder(f).Encode(overlayJSON); err != nil { 154 | return err 155 | } 156 | defer func() { 157 | rerr = multierr.Append(rerr, f.Close()) 158 | }() 159 | 160 | opts := make([]string, 0, flags.NFlag()) 161 | flags.Visit(func(f *flag.Flag) { 162 | opts = append(opts, fmt.Sprintf("-%s=%v", f.Name, f.Value)) 163 | }) 164 | 165 | goargs := append([]string{"test", "-overlay", f.Name()}, append(opts, flags.Args()...)...) 166 | cmd := exec.Command("go", goargs...) 167 | cmd.Stdout = m.Output 168 | cmd.Stderr = m.ErrOutput 169 | cmd.Stdin = m.Input 170 | cmd.Dir = m.Dir 171 | 172 | if err := cmd.Run(); err != nil { 173 | return err 174 | } 175 | 176 | return nil 177 | } 178 | 179 | func (m *Mocknn) printOverlayJSON(args []string) (rerr error) { 180 | pkgs, err := m.load(args) 181 | if err != nil { 182 | return err 183 | } 184 | 185 | g := &overlay.Generator{ 186 | Pkgs: pkgs, 187 | } 188 | 189 | overlayJSON, err := g.Generate() 190 | if err != nil { 191 | return err 192 | } 193 | 194 | f, err := os.CreateTemp("", "mocknn-overlay-*.json") 195 | if err != nil { 196 | return err 197 | } 198 | defer func() { 199 | rerr = multierr.Append(rerr, f.Close()) 200 | }() 201 | 202 | if err := json.NewEncoder(f).Encode(overlayJSON); err != nil { 203 | return err 204 | } 205 | 206 | if _, err := fmt.Fprint(m.Output, f.Name()); err != nil { 207 | return err 208 | } 209 | 210 | return nil 211 | } 212 | -------------------------------------------------------------------------------- /internal/overlay/gen.go: -------------------------------------------------------------------------------- 1 | package overlay 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/format" 7 | "go/parser" 8 | "go/token" 9 | "go/types" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | 14 | "go.uber.org/multierr" 15 | "golang.org/x/tools/go/ast/astutil" 16 | "golang.org/x/tools/go/packages" 17 | ) 18 | 19 | const directive = "//mocknn:" 20 | 21 | type Generator struct { 22 | Dir string 23 | Pkgs []*packages.Package 24 | Overlay *packages.OverlayJSON 25 | } 26 | 27 | func (g *Generator) Generate() (*packages.OverlayJSON, error) { 28 | r := &replacer{ 29 | pkgs: g.Pkgs, 30 | dir: g.Dir, 31 | json: g.Overlay, 32 | replaces: make(map[ast.Node]ast.Node), 33 | deletes: make(map[ast.Node]bool), 34 | } 35 | 36 | if r.dir == "" { 37 | tmpdir, err := os.MkdirTemp("", "mocknn-*") 38 | if err != nil { 39 | return nil, err 40 | } 41 | r.dir = tmpdir 42 | } 43 | 44 | if r.json == nil { 45 | r.json = &packages.OverlayJSON{ 46 | Replace: make(map[string]string), 47 | } 48 | } 49 | 50 | for _, pkg := range g.Pkgs { 51 | if err := r.replacePkg(pkg); err != nil { 52 | return nil, err 53 | } 54 | } 55 | 56 | return r.json, nil 57 | } 58 | 59 | type replacer struct { 60 | dir string 61 | pkgs []*packages.Package 62 | json *packages.OverlayJSON 63 | replaces map[ast.Node]ast.Node 64 | deletes map[ast.Node]bool 65 | } 66 | 67 | func (r *replacer) replacePkg(pkg *packages.Package) error { 68 | 69 | for _, file := range pkg.Syntax { 70 | if err := r.replaceFile(pkg, file); err != nil { 71 | return err 72 | } 73 | } 74 | 75 | for _, file := range pkg.Syntax { 76 | if err := r.createFile(pkg.Fset, file); err != nil { 77 | return err 78 | } 79 | } 80 | 81 | return nil 82 | } 83 | 84 | func (r *replacer) replaceFile(pkg *packages.Package, file *ast.File) error { 85 | 86 | cmap := ast.NewCommentMap(pkg.Fset, file, file.Comments) 87 | for _, decl := range file.Decls { 88 | switch decl := decl.(type) { 89 | case *ast.GenDecl: 90 | switch decl.Tok { 91 | case token.TYPE: 92 | r.replaceType(pkg, cmap, file, decl) 93 | case token.VAR, token.CONST: 94 | r.replaceValue(pkg, cmap, decl) 95 | } 96 | case *ast.FuncDecl: 97 | if err := r.replaceFunc(pkg, cmap, file, decl); err != nil { 98 | return err 99 | } 100 | } 101 | 102 | } 103 | 104 | return nil 105 | } 106 | 107 | func (r *replacer) createFile(fset *token.FileSet, file *ast.File) (rerr error) { 108 | 109 | tfile := fset.File(file.Pos()) 110 | if tfile == nil { 111 | return nil 112 | } 113 | 114 | var fixed bool 115 | newfile := astutil.Apply(file, func(c *astutil.Cursor) bool { 116 | if r.deletes[c.Node()] { 117 | c.Delete() 118 | fixed = true 119 | return false 120 | } 121 | 122 | if n := r.replaces[c.Node()]; n != nil { 123 | c.Replace(n) 124 | fixed = true 125 | return false 126 | } 127 | 128 | return true 129 | }, nil) 130 | 131 | if !fixed { 132 | return nil 133 | } 134 | 135 | orgFileName := tfile.Name() 136 | dstFileName := filepath.Join(r.dir, filepath.Base(orgFileName)) 137 | f, err := os.Create(dstFileName) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | defer func() { 143 | rerr = multierr.Append(rerr, f.Close()) 144 | }() 145 | 146 | if err := format.Node(f, fset, newfile); err != nil { 147 | return err 148 | } 149 | 150 | r.json.Replace[orgFileName] = dstFileName 151 | 152 | return nil 153 | } 154 | 155 | func (r *replacer) replaceType(pkg *packages.Package, cmap ast.CommentMap, file *ast.File, decl *ast.GenDecl) { 156 | org, ok := parseComment(cmap, decl) 157 | if !ok { 158 | return 159 | } 160 | 161 | for _, spec := range decl.Specs { 162 | spec, _ := spec.(*ast.TypeSpec) 163 | if spec == nil || spec.TypeParams != nil { 164 | // TODO: Fix for go.dev/issue/46477 in Go 1.20 165 | continue 166 | } 167 | 168 | org := org 169 | if o, ok := parseComment(cmap, spec); ok { 170 | org = o 171 | } 172 | 173 | if org == "" { 174 | continue 175 | } 176 | 177 | orgspec := typeSepc(pkg, pkg.Types.Scope().Lookup(org)) 178 | if orgspec == nil { 179 | continue 180 | } 181 | 182 | typ, _ := pkg.TypesInfo.TypeOf(orgspec.Name).(*types.Named) 183 | if typ == nil { 184 | continue 185 | } 186 | 187 | r.replaces[orgspec] = &ast.TypeSpec{ 188 | Doc: orgspec.Doc, 189 | Name: orgspec.Name, 190 | // TODO: Fix for go.dev/issue/46477 in Go 1.20 191 | TypeParams: nil, 192 | // +1 is space 193 | Assign: orgspec.Name.Pos() + token.Pos(len(orgspec.Name.Name)) + 1, 194 | Type: ast.NewIdent(spec.Name.Name), 195 | Comment: orgspec.Comment, 196 | } 197 | 198 | for i := 0; i < typ.NumMethods(); i++ { 199 | m := typ.Method(i) 200 | n := funcDecl(pkg, m) 201 | if n != nil { 202 | r.deletes[n] = true 203 | } 204 | } 205 | } 206 | } 207 | 208 | func (r *replacer) replaceValue(pkg *packages.Package, cmap ast.CommentMap, decl *ast.GenDecl) { 209 | org, ok := parseComment(cmap, decl) 210 | if !ok { 211 | return 212 | } 213 | 214 | for _, spec := range decl.Specs { 215 | spec, _ := spec.(*ast.ValueSpec) 216 | if spec == nil || len(spec.Names) != 1 { 217 | continue 218 | } 219 | 220 | org := org 221 | if o, ok := parseComment(cmap, spec); ok { 222 | org = o 223 | } 224 | 225 | if org == "" { 226 | continue 227 | } 228 | 229 | orgspec := valueSepc(pkg, pkg.Types.Scope().Lookup(org)) 230 | if orgspec == nil { 231 | continue 232 | } 233 | 234 | r.replaces[orgspec] = &ast.ValueSpec{ 235 | Doc: orgspec.Doc, 236 | Names: []*ast.Ident{ast.NewIdent(org)}, 237 | Type: orgspec.Type, 238 | Values: []ast.Expr{ast.NewIdent(spec.Names[0].Name)}, 239 | Comment: orgspec.Comment, 240 | } 241 | } 242 | } 243 | 244 | func (r *replacer) replaceFunc(pkg *packages.Package, cmap ast.CommentMap, file *ast.File, decl *ast.FuncDecl) error { 245 | org, ok := parseComment(cmap, decl) 246 | if !ok { 247 | return nil 248 | } 249 | 250 | orgObj := pkg.Types.Scope().Lookup(org) 251 | orgdecl := funcDecl(pkg, orgObj) 252 | if orgdecl == nil { 253 | return nil 254 | } 255 | 256 | funcType, _ := orgObj.Type().(*types.Signature) 257 | mockType, _ := pkg.TypesInfo.TypeOf(decl.Name).(*types.Signature) 258 | if funcType == nil || mockType == nil { 259 | return nil 260 | } 261 | 262 | args := make([]string, 0, funcType.Params().Len()) 263 | for _, f := range orgdecl.Type.Params.List { 264 | for _, n := range f.Names { 265 | args = append(args, n.Name) 266 | } 267 | } 268 | 269 | callexpr, err := parser.ParseExpr(fmt.Sprintf("%s(%s)", decl.Name.Name, strings.Join(args, ","))) 270 | if err != nil { 271 | return fmt.Errorf("replace func: %w", err) 272 | } 273 | 274 | var stmt ast.Stmt 275 | if funcType.Results().Len() == 0 { 276 | stmt = &ast.ExprStmt{X: callexpr} 277 | } else { 278 | stmt = &ast.ReturnStmt{ 279 | Results: []ast.Expr{callexpr}, 280 | } 281 | } 282 | 283 | body := &ast.BlockStmt{ 284 | Lbrace: orgdecl.Body.Lbrace, 285 | List: []ast.Stmt{stmt}, 286 | Rbrace: orgdecl.Body.Lbrace + callexpr.End(), 287 | } 288 | 289 | r.replaces[orgdecl.Body] = body 290 | 291 | return nil 292 | } 293 | 294 | func typeSepc(pkg *packages.Package, o types.Object) *ast.TypeSpec { 295 | switch o.(type) { 296 | case *types.TypeName: // ok 297 | default: 298 | return nil 299 | } 300 | 301 | id := ident(pkg.TypesInfo, o) 302 | if id == nil { 303 | return nil 304 | } 305 | 306 | for _, file := range pkg.Syntax { 307 | for _, decl := range file.Decls { 308 | decl, _ := decl.(*ast.GenDecl) 309 | if decl == nil || decl.Tok != token.TYPE { 310 | continue 311 | } 312 | 313 | for _, spec := range decl.Specs { 314 | spec, _ := spec.(*ast.TypeSpec) 315 | if spec != nil && spec.Name == id { 316 | return spec 317 | } 318 | } 319 | } 320 | } 321 | 322 | return nil 323 | } 324 | 325 | func valueSepc(pkg *packages.Package, o types.Object) *ast.ValueSpec { 326 | switch o.(type) { 327 | case *types.Var, *types.Const: // ok 328 | default: 329 | return nil 330 | } 331 | 332 | id := ident(pkg.TypesInfo, o) 333 | if id == nil { 334 | return nil 335 | } 336 | 337 | for _, file := range pkg.Syntax { 338 | for _, decl := range file.Decls { 339 | decl, _ := decl.(*ast.GenDecl) 340 | if decl == nil || (decl.Tok != token.VAR && decl.Tok != token.CONST) { 341 | continue 342 | } 343 | 344 | for _, spec := range decl.Specs { 345 | spec, _ := spec.(*ast.ValueSpec) 346 | if spec != nil && len(spec.Names) == 1 && spec.Names[0] == id { 347 | return spec 348 | } 349 | } 350 | } 351 | } 352 | 353 | return nil 354 | } 355 | 356 | func funcDecl(pkg *packages.Package, o types.Object) *ast.FuncDecl { 357 | switch o.(type) { 358 | case *types.Func: // ok 359 | default: 360 | return nil 361 | } 362 | 363 | id := ident(pkg.TypesInfo, o) 364 | if id == nil { 365 | return nil 366 | } 367 | 368 | for _, file := range pkg.Syntax { 369 | for _, decl := range file.Decls { 370 | decl, _ := decl.(*ast.FuncDecl) 371 | if decl != nil && decl.Name == id { 372 | return decl 373 | } 374 | } 375 | } 376 | 377 | return nil 378 | } 379 | 380 | func ident(info *types.Info, o types.Object) *ast.Ident { 381 | for id, obj := range info.Defs { 382 | if o == obj { 383 | return id 384 | } 385 | } 386 | return nil 387 | } 388 | 389 | func parseComment(cmap ast.CommentMap, n ast.Node) (mock string, ok bool) { 390 | cgs, ok := cmap[n] 391 | if !ok { 392 | return "", false 393 | } 394 | 395 | for _, cg := range cgs { 396 | for _, c := range cg.List { 397 | i := strings.Index(c.Text, directive) 398 | if i < 0 { 399 | continue 400 | } 401 | i += len(directive) 402 | s := strings.Split(strings.TrimSpace(c.Text[i:]), " ") 403 | return s[0], true 404 | } 405 | } 406 | 407 | return "", false 408 | } 409 | -------------------------------------------------------------------------------- /internal/overlay/gen_test.go: -------------------------------------------------------------------------------- 1 | package overlay_test 2 | 3 | import ( 4 | "flag" 5 | "path/filepath" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/tenntenn/golden" 10 | "github.com/tenntenn/mocknn/internal/overlay" 11 | "golang.org/x/tools/go/packages" 12 | ) 13 | 14 | var ( 15 | flagUpdate bool 16 | ) 17 | 18 | func init() { 19 | flag.BoolVar(&flagUpdate, "update", false, "update golden files") 20 | } 21 | 22 | func TestGenerator_Generate(t *testing.T) { 23 | t.Parallel() 24 | 25 | cases := map[string]struct { 26 | wantErr bool 27 | }{ 28 | "normal": {false}, 29 | } 30 | 31 | goldendir := filepath.Join(testdata(t), "golden") 32 | 33 | for name, tt := range cases { 34 | name, tt := name, tt 35 | t.Run(name, func(t *testing.T) { 36 | t.Parallel() 37 | 38 | tmpdir := t.TempDir() 39 | g := &overlay.Generator{ 40 | Pkgs: load(t, name), 41 | Dir: tmpdir, 42 | } 43 | 44 | got, err := g.Generate() 45 | 46 | switch { 47 | case err == nil && tt.wantErr: 48 | t.Fatal("expected error does not occur") 49 | case err != nil && !tt.wantErr: 50 | t.Fatal("unexpected error:", err) 51 | } 52 | 53 | for key, val := range got.Replace { 54 | got.Replace[key] = strings.TrimPrefix(val, tmpdir+string([]rune{filepath.Separator})) 55 | } 56 | 57 | gotDir := strings.ReplaceAll(golden.Txtar(t, tmpdir), "-- "+filepath.ToSlash(tmpdir), "-- ") 58 | 59 | name := strings.ReplaceAll(t.Name(), "/", "-") 60 | if flagUpdate { 61 | golden.Update(t, goldendir, name+"-overlay-json", got) 62 | golden.Update(t, goldendir, name+"-mock-files", gotDir) 63 | return 64 | } 65 | 66 | if diff := golden.Diff(t, goldendir, name+"-overlay-json", got); diff != "" { 67 | t.Errorf("overlayJSON\n:%s", diff) 68 | } 69 | 70 | if diff := golden.Diff(t, goldendir, name+"-mock-files", gotDir); diff != "" { 71 | t.Errorf("mock files\n:%s", diff) 72 | } 73 | }) 74 | } 75 | } 76 | 77 | func testdata(t *testing.T) string { 78 | dir, err := filepath.Abs("testdata") 79 | if err != nil { 80 | t.Fatal("unexpected error:", err) 81 | } 82 | return dir 83 | } 84 | 85 | func load(t *testing.T, pkg string) []*packages.Package { 86 | config := &packages.Config{ 87 | Dir: filepath.Join(testdata(t), "src", pkg), 88 | Tests: true, 89 | Mode: packages.NeedName | packages.NeedTypes | 90 | packages.NeedSyntax | packages.NeedTypesInfo | 91 | packages.NeedModule, 92 | } 93 | 94 | pkgs, err := packages.Load(config, "./...") 95 | if err != nil { 96 | t.Fatal("unexpected error:", err) 97 | } 98 | 99 | packages.Visit(pkgs, nil, func(pkg *packages.Package) { 100 | for _, err := range pkg.Errors { 101 | t.Fatal("unexpected error:", pkg, err) 102 | } 103 | }) 104 | 105 | return pkgs 106 | } 107 | -------------------------------------------------------------------------------- /internal/overlay/testdata/golden/TestGenerator_Generate-normal-mock-files.golden: -------------------------------------------------------------------------------- 1 | -- a.go -- 2 | package normal 3 | 4 | type T = MockT 5 | 6 | func F(t *T) { 7 | MockF(t) 8 | 9 | } 10 | -------------------------------------------------------------------------------- /internal/overlay/testdata/golden/TestGenerator_Generate-normal-overlay-json.golden: -------------------------------------------------------------------------------- 1 | {"replace":{"/Users/tenntenn/repos/tenntenn/mocknn/internal/overlay/testdata/src/normal/a.go":"a.go"}} 2 | -------------------------------------------------------------------------------- /internal/overlay/testdata/src/normal/a.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | type T struct { 4 | n int 5 | } 6 | 7 | func (t *T) M() { 8 | println(t.n) 9 | } 10 | 11 | func F(t *T) { 12 | t.M() 13 | } 14 | -------------------------------------------------------------------------------- /internal/overlay/testdata/src/normal/go.mod: -------------------------------------------------------------------------------- 1 | module normal 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /internal/overlay/testdata/src/normal/mock_test.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | //mocknn: T 4 | type MockT struct { 5 | m int 6 | } 7 | 8 | func (t *MockT) M() { 9 | println(t.m) 10 | } 11 | 12 | //mocknn: F 13 | func MockF(t *T) { 14 | t.M() 15 | } 16 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "os" 6 | 7 | "github.com/tenntenn/mocknn/internal" 8 | ) 9 | 10 | //go:embed version.txt 11 | var version string 12 | 13 | func main() { 14 | os.Exit(internal.Main(version, os.Args[1:])) 15 | } 16 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | v0.0.1 --------------------------------------------------------------------------------