├── .gitignore
├── LICENSE
├── Makefile
├── README.markdown
├── _examples
├── store.generated.go
└── store.go
├── codegen.go
├── interface.go
├── main.go
├── media
└── logo.png
├── strings.go
├── strings_test.go
└── wanted.go
/.gitignore:
--------------------------------------------------------------------------------
1 | go-interface-fuzzer
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Pusher
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all default fmt build test clean
2 |
3 | all: default
4 |
5 | default: fmt build test
6 |
7 | fmt:
8 | go fmt ./...
9 |
10 | build: go-interface-fuzzer
11 |
12 | test:
13 | go test ./...
14 |
15 | clean:
16 | rm go-interface-fuzzer
17 |
18 | go-interface-fuzzer: *.go
19 | go build -o $@
20 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Go Interface Fuzzer is a fuzzy testing tool for Go interfaces used [@Pusher](https://github.com/pusher). The
7 | goal of the project is to make it easier for developers to have
8 | confidence in the correctness of their programs by combining
9 | randomised testing with reference semantics.
10 |
11 | Given an interface, a reference implementation, and some hints on how
12 | to generate function parameters and compare function return values,
13 | Go Interface Fuzzer will generate testing functions which can be
14 | used to check that the behaviour of an arbitrary other type
15 | implementing the interface behaves the same.
16 |
17 | See the [`_examples` directory](https://github.com/pusher/go-interface-fuzzer/tree/master/_examples) for a complete self-contained example.
18 |
19 | ## Table of Contents
20 |
21 | - [Project Status](#project-status)
22 | - [Getting Started](#getting-started)
23 | - [Installing](#installing)
24 | - [Usage](#usage)
25 | - [Incorporating into the build](#incorporating-into-the-build)
26 | - [Directives](#directives)
27 | - [`@fuzz interface` (required)](#fuzz-interface-required)
28 | - [`@known correct` (required)](#known-correct-required)
29 | - [`@invariant`](#invariant)
30 | - [`@comparison`](#comparison)
31 | - [`@generator state`](#generator-state)
32 | - [Defaults](#defaults)
33 | - [Other Uses](#other-uses)
34 | - [Regression testing](#regression-testing)
35 | - [Assertion-only testing](#assertion-only-testing)
36 |
37 | ## Project Status
38 |
39 | The tool is stable and the interface fixed. New functionality,
40 | directives, and command-line arguments may be added, but existing
41 | usages will not suddenly stop working.
42 |
43 | ## Getting Started
44 |
45 | ### Installing
46 |
47 | To start using Go Interface Fuzzer, install Go and run `go get`:
48 |
49 | ```sh
50 | $ go get github.com/pusher/go-interface-fuzzer
51 | ```
52 |
53 | This will install the `go-interface-fuzzer` command-line tool into
54 | your `$GOBIN` path, which defaults to `$GOPATH/bin`.
55 |
56 |
57 | ### Usage
58 |
59 | The generated code in the `_examples` directory is produced by
60 |
61 | ```bash
62 | go-interface-fuzzer -c -o -f _examples/store.generated.go _examples/store.go
63 | ```
64 |
65 | - The `-c` flag generates a **c**omplete source file, complete with
66 | package name and imports.
67 | - The `-o` flag writes the **o**utput to the filename given by the
68 | `-f` flag.
69 | - The `-f` flag specifies the **f**ilename to use when writing output
70 | and resolving imports.
71 |
72 | The generated code can be customised further, see the full help text
73 | (`go-interface-fuzzer --help`) for a complete flag listing.
74 |
75 | The tool generates three functions, named after the interface used.
76 | With the example file, the following functions are be produced:
77 |
78 | - `FuzzStoreWith(reference Store, test Store, rand *rand.Rand, maxops uint) error`
79 |
80 | Create a new reference store and test store, apply a
81 | randomly-generated list of actions, and bail out on inconsistency.
82 |
83 | - `FuzzStore(makeTest (func(int) Store), rand *rand.Rand, maxops uint) error`
84 |
85 | Call `FuzzStoreWithReference` with the ModelStore as the reference
86 | one.
87 |
88 | - `FuzzTestStore(makeTest (func(int) Store), t *testing.T)`
89 |
90 | A test case parameterised by the store generating function, with a
91 | default maxops of 100.
92 |
93 | By default Go Interface Fuzzer generates an incomplete fragment: no
94 | package name, no imports, just the three testing functions per
95 | interface.
96 |
97 |
98 | #### Incorporating into the build
99 |
100 | Go makes adding a code generation stage to your build process quite
101 | simple, with the `go generate` tool. To incorporate into your build,
102 | add a comment to your source file:
103 |
104 | ```go
105 | //go:generate go-interface-fuzzer -c -o -f output_file.go input_file.go
106 | ```
107 |
108 | Typically this would be added to the same file which defines the
109 | interface and provides the processing directives (see the next
110 | section), but that isn't required.
111 |
112 | To then actually generate the file, run `go generate`. It is not done
113 | for you as part of `go build`.
114 |
115 | For further information on code generation in Go see
116 | "[Generating code](https://blog.golang.org/generate)", on the Go blog.
117 |
118 |
119 | ### Directives
120 |
121 | An interface must be marked-up with some processing directives to
122 | direct the tool. These directives are given inside a single multi-line
123 | comment.
124 |
125 | The minimum is just indicating that a fuzz tester should be generated
126 | for the interface, and how to produce a new value of the reference
127 | implementation type. For example:
128 |
129 | ```go
130 | /*
131 | @fuzz interface: Store
132 | @known correct: makeReferenceStore int
133 | */
134 | type Store interface {
135 | // ...
136 | }
137 | ```
138 |
139 | The fuzzer definition does not need to be immediately next to the
140 | interface, it can be anywhere in the source file.
141 |
142 | See the `_examples` directory for a complete self-contained example
143 | using most of the directives.
144 |
145 |
146 | #### `@fuzz interface` (required)
147 |
148 | This directive begins the definition of a fuzzer, and gives the name
149 | of the interface to test.
150 |
151 | **Example:** `@fuzz interface: Store`
152 |
153 | **Argument syntax:** `InterfaceName`
154 |
155 |
156 | #### `@known correct` (required)
157 |
158 | This directive gives a function to produce a new value of the
159 | reference implementation. It specifies the parameters of the function,
160 | and whether the return type is a value or a pointer type.
161 |
162 | The generated fuzzing function will expect a function argument with
163 | the same parameters to create a new value of the type under test.
164 |
165 | **Example:** `@known correct: makeReferenceStore int`
166 |
167 | **Argument syntax:** `[&] FunctionName [ArgType1 ... ArgTypeN]`
168 |
169 | The presence of a `&` means that this returns a value rather than a
170 | pointer, and so a reference must be made to it.
171 |
172 |
173 | #### `@invariant`
174 |
175 | This directive specifies a property that must always hold. It is only
176 | checked for the test implementation, not the reference implementation.
177 |
178 | **Example:** `@invariant: %var.NumEntries() == len(%var.AsSlice())`
179 |
180 | **Argument syntax:** `Expression`
181 |
182 | The argument is a Go expression that evaluates to a boolean, with
183 | `%var` replaced with the variable name.
184 |
185 |
186 | #### `@comparison`
187 |
188 | This directive specifies a function to use to compare two values. If
189 | not specified the reflection package is used.
190 |
191 | **Example:** `@comparison: *MessageIterator:CompareWith`
192 |
193 | **Argument syntax:** `(Type:FunctionName | FunctionName Type)`
194 |
195 | In the method form, the target of the comparison is passed as the sole
196 | parameter; in the function form both are passed as parameters.
197 |
198 |
199 | #### `@generator`
200 |
201 | This directive specifies a function to generate a value of the
202 | required type. It is passed a PRNG of type `*rand.Rand`. If no
203 | generator for a type is specified, the tool will attempt to produce a
204 | default; and report an error otherwise.
205 |
206 | **Example:** `@generator: GenerateChannel model.Channel`
207 |
208 | **Argument syntax:** `[!] FunctionName Type`
209 |
210 | The presence of a `!` means that this is a stateful function: it is
211 | also passed a state parameter and is expected to return a new state as
212 | its second result.
213 |
214 |
215 | #### `@generator state`
216 |
217 | This directive supplies an initial state for stateful generators. It
218 | must be given if any generators are stateful. The initial state is any
219 | legal Go expression; it is just copied verbatim into the generated
220 | code.
221 |
222 | **Example:** `@generator state: InitialGeneratorState`
223 |
224 | **Argument syntax:** `Expression`
225 |
226 |
227 | ### Defaults
228 |
229 | The following default **comparison** operations are used if not
230 | overridden:
231 |
232 | | Type | Comparison |
233 | |-----------------|----------------------------------------------|
234 | | `error` | Equal if both values are `nil` or non-`nil`. |
235 | | Everything else | `reflect.DeepEqual` |
236 |
237 | The following default **generator** functions are used if not
238 | overridden:
239 |
240 | | Type | Generator |
241 | |-----------------|---------------------------------------------------------------------|
242 | | `bool` | `rand.Intn(2) == 0` |
243 | | `byte` | `byte(rand.Uint32())` |
244 | | `complex64` | `complex(float32(rand.NormFloat64()), float32(rand.NormFloat64()))` |
245 | | `complex128` | `complex(rand.NormFloat64(), rand.NormFloat64())` |
246 | | `float32` | `float32(rand.NormFloat64())` |
247 | | `float64` | `rand.NormFloat64()` |
248 | | `int` | `rand.Int()` |
249 | | `int8` | `int8(rand.Int())` |
250 | | `int16` | `int16(rand.Int())` |
251 | | `int32` | `rand.Int31()` |
252 | | `int64` | `rand.Int63()` |
253 | | `rune` | `rune(rand.Int31())` |
254 | | `uint` | `uint(rand.Uint32())` |
255 | | `uint8` | `uint8(rand.Uint32())` |
256 | | `uint16` | `uint16(rand.Uint32())` |
257 | | `uint32` | `rand.Uint32()` |
258 | | `uint64` | `(uint64(rand.Uint32()) << 32) | uint64(rand.Uint32())` |
259 | | Everything else | **No default** |
260 |
261 |
262 | ## Other Uses
263 | ### Regression testing
264 |
265 | Although the motivating use-case for Go Interface Fuzzer was an
266 | interface with two implementations that should have identical
267 | behaviour, there is nothing preventing the use of multiple *versions*
268 | of the *same* implementation. This facilitates regression testing,
269 | although at the cost of needing to keep the old implementation around.
270 |
271 | Here are the concrete steps you would need to follow to do this:
272 |
273 | 1. Make a copy of your current implementation, with a new name.
274 | 2. Write a new implementation.
275 | 3. Use the current implementation as the reference implementation for
276 | the generated fuzzer.
277 |
278 | This isn't quite the same as reference correctness, as any bugs in the
279 | old implementation which the new fixes will be reported as an error in
280 | the new implementation.
281 |
282 |
283 | ### Assertion-only testing
284 |
285 | By supplying the same implementation as both the reference and the
286 | test implementation, the fuzz tester will simply check invariants.
287 | Although, it'll be a little slow, because every operation is being
288 | performed twice.
289 |
290 | In the future, there will probably be an "invariant-only" mode of
291 | operation.
292 |
--------------------------------------------------------------------------------
/_examples/store.generated.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "math/rand"
7 | "reflect"
8 | "testing"
9 | )
10 |
11 | // Store
12 |
13 | func FuzzTestStore(makeTest func(int) Store, t *testing.T) {
14 | rand := rand.New(rand.NewSource(0))
15 |
16 | err := FuzzStore(makeTest, rand, 100)
17 |
18 | if err != nil {
19 | t.Error(err)
20 | }
21 | }
22 |
23 | func FuzzStore(makeTest func(int) Store, rand *rand.Rand, max uint) error {
24 | var (
25 | argInt int
26 | )
27 |
28 | argInt = rand.Int()
29 |
30 | expectedStore := makeReferenceStore(argInt)
31 | actualStore := makeTest(argInt)
32 |
33 | return FuzzStoreWith(&expectedStore, actualStore, rand, max)
34 | }
35 |
36 | func FuzzStoreWith(reference Store, test Store, rand *rand.Rand, maxops uint) error {
37 | // Create initial state
38 | state := uint(0)
39 |
40 | for i := uint(0); i < maxops; i++ {
41 | // Pick a random number between 0 and the number of methods of the interface. Then do that method on
42 | // both, check for discrepancy, and bail out on error. Simple!
43 |
44 | actionToPerform := rand.Intn(6)
45 |
46 | switch actionToPerform {
47 | case 0:
48 | // Call the method on both implementations
49 | var (
50 | argMessage Message
51 | )
52 |
53 | argMessage, state = generateMessage(rand, state)
54 |
55 | expectedError := reference.Put(argMessage)
56 | actualError := test.Put(argMessage)
57 |
58 | // And check for discrepancies.
59 | if !((expectedError == nil) == (actualError == nil)) {
60 | return fmt.Errorf("inconsistent result in Put\nexpected: %v\nactual: %v", expectedError, actualError)
61 | }
62 | case 1:
63 | // Call the method on both implementations
64 | var (
65 | argID ID
66 | argChannel Channel
67 | )
68 |
69 | argID, state = generateID(rand, state)
70 | argChannel = generateChannel(rand)
71 |
72 | expectedID, expectedMessage := reference.EntriesSince(argID, argChannel)
73 | actualID, actualMessage := test.EntriesSince(argID, argChannel)
74 |
75 | // And check for discrepancies.
76 | if !reflect.DeepEqual(expectedID, actualID) {
77 | return fmt.Errorf("inconsistent result in EntriesSince\nexpected: %v\nactual: %v", expectedID, actualID)
78 | }
79 | if !reflect.DeepEqual(expectedMessage, actualMessage) {
80 | return fmt.Errorf("inconsistent result in EntriesSince\nexpected: %v\nactual: %v", expectedMessage, actualMessage)
81 | }
82 | case 2:
83 | // Call the method on both implementations
84 | expectedID := reference.MostRecentID()
85 | actualID := test.MostRecentID()
86 |
87 | // And check for discrepancies.
88 | if !reflect.DeepEqual(expectedID, actualID) {
89 | return fmt.Errorf("inconsistent result in MostRecentID\nexpected: %v\nactual: %v", expectedID, actualID)
90 | }
91 | case 3:
92 | // Call the method on both implementations
93 | expectedInt := reference.NumEntries()
94 | actualInt := test.NumEntries()
95 |
96 | // And check for discrepancies.
97 | if !reflect.DeepEqual(expectedInt, actualInt) {
98 | return fmt.Errorf("inconsistent result in NumEntries\nexpected: %v\nactual: %v", expectedInt, actualInt)
99 | }
100 | case 4:
101 | // Call the method on both implementations
102 | expectedMessage := reference.AsSlice()
103 | actualMessage := test.AsSlice()
104 |
105 | // And check for discrepancies.
106 | if !reflect.DeepEqual(expectedMessage, actualMessage) {
107 | return fmt.Errorf("inconsistent result in AsSlice\nexpected: %v\nactual: %v", expectedMessage, actualMessage)
108 | }
109 | case 5:
110 | // Call the method on both implementations
111 | expectedInt := reference.MessageLimit()
112 | actualInt := test.MessageLimit()
113 |
114 | // And check for discrepancies.
115 | if !reflect.DeepEqual(expectedInt, actualInt) {
116 | return fmt.Errorf("inconsistent result in MessageLimit\nexpected: %v\nactual: %v", expectedInt, actualInt)
117 | }
118 | }
119 |
120 | if !(reference.NumEntries() == len(reference.AsSlice())) {
121 | return errors.New("invariant violated: %var.NumEntries() == len(%var.AsSlice())")
122 | }
123 |
124 | if !(reference.NumEntries() <= reference.MessageLimit()) {
125 | return errors.New("invariant violated: %var.NumEntries() <= %var.MessageLimit()")
126 | }
127 |
128 | }
129 |
130 | return nil
131 | }
132 |
--------------------------------------------------------------------------------
/_examples/store.go:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | A very simple message store example: messages are sorted into
4 | "channels", and have an associated "ID". A message cannot directly be
5 | retrieved, but all messages since an ID can be retrieved: either as an
6 | iterator or as a slice.
7 |
8 | This assumes a type called ReferenceStore and a function
9 | NewReferenceStore are in scope.
10 |
11 | */
12 |
13 | package example
14 |
15 | import (
16 | "math/rand"
17 | "reflect"
18 | )
19 |
20 | /*
21 | @fuzz interface: Store
22 |
23 | @known correct: & makeReferenceStore int
24 |
25 | @invariant: %var.NumEntries() == len(%var.AsSlice())
26 | @invariant: %var.NumEntries() <= %var.MessageLimit()
27 |
28 | @generator state: uint(0)
29 |
30 | @generator: generateChannel Channel
31 | @generator: ! generateID ID
32 | @generator: ! generateMessage Message
33 | */
34 | type Store interface {
35 | // Inserts an entry in the store. Returns an error if an entry with greater or
36 | // equal ID was already inserted.
37 | Put(msg Message) error
38 |
39 | // Returns a slice of all messages in the specified channels, from the
40 | // specified ID to the message with most recent ID. All messages will have IDs
41 | // such that `sinceID < ID <= mostRecentID`.
42 | EntriesSince(sinceID ID, channel Channel) (ID, []Message)
43 |
44 | // Returns the ID of the most recently inserted message.
45 | MostRecentID() ID
46 |
47 | // Returns the number of messages in the store.
48 | NumEntries() int
49 |
50 | // Returns all messages across all channels as a single slice,
51 | // sorted by ID.
52 | AsSlice() []Message
53 |
54 | // Returns the maximum number of messages in the store.
55 | MessageLimit() int
56 | }
57 |
58 | type Message struct {
59 | // Each message has a unique ID.
60 | ID ID
61 |
62 | // A message belongs to a specific channel.
63 | Channel Channel
64 |
65 | // And has a body
66 | Body string
67 | }
68 |
69 | type Channel string
70 |
71 | type ID uint64
72 |
73 | // Create a new clean ReferenceStore for testing purposes.
74 | func makeReferenceStore(capacity int) ReferenceStore {
75 | return NewReferenceStore(capacity, []Message{})
76 | }
77 |
78 | // Generate a channel name. Use a short string.
79 | func generateChannel(rand *rand.Rand) Channel {
80 | return Channel(randomString(rand, 1+rand.Intn(4)))
81 | }
82 |
83 | // Generate an ID. Randomly generate some across the full range of
84 | // rand.Int. Sometimes use only those that have been seen before. This
85 | // is so that EntriesSince and EntriesSinceIter have a good chance of
86 | // actually returning something (whilst not disregarding the "error"
87 | // case where the ID is invalid).
88 | func generateID(rand *rand.Rand, maxSoFar uint) (ID, uint) {
89 | newid := uint(rand.Uint32()) % 64
90 |
91 | if rand.Intn(2) == 0 {
92 | newid = uint(rand.Uint32()) % maxSoFar
93 | }
94 |
95 | return ID(newid), maxSoFar
96 | }
97 |
98 | // Generate an ID message. Randomly generate some. Use a
99 | // monotonically-increasing ID for others. This is because Put
100 | // requires that, so if IDs were totally random, this wouldn't be
101 | // likely to produce particularly good results.
102 | func generateMessage(rand *rand.Rand, maxSoFar uint) (Message, uint) {
103 | newid := uint(rand.Uint32()) % 64
104 |
105 | msg := Message{
106 | Channel: generateChannel(rand),
107 | Data: MessageData(randomString(rand, 1+rand.Intn(4))),
108 | }
109 |
110 | if rand.Intn(2) == 0 {
111 | newid = maxSoFar + 1
112 | }
113 |
114 | if newid > maxSoFar {
115 | maxSoFar = newid
116 | }
117 |
118 | return Message{ID: ID(newid), Message: msg}, maxSoFar
119 | }
120 |
121 | // Generate a random string
122 | func randomString(rand *rand.Rand, n int) string {
123 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
124 | b := make([]rune, n)
125 | for i := range b {
126 | b[i] = letterRunes[rand.Intn(len(letterRunes))]
127 | }
128 | return string(b)
129 | }
130 |
--------------------------------------------------------------------------------
/codegen.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "go/ast"
8 | "strconv"
9 | "strings"
10 | "text/template"
11 | "unicode"
12 |
13 | goimports "golang.org/x/tools/imports"
14 | )
15 |
16 | // CodeGenOptions specifies the behaviour for the top-level code
17 | // generator
18 | type CodeGenOptions struct {
19 | // The filename to use when automatically resolving
20 | // imports. If unset by the command-line arguments, defaults
21 | // to the filename of the source file.
22 | Filename string
23 |
24 | // The package to use in the generated output. If unset by the
25 | // command-line arguments, defaults to the package of the
26 | // source file.
27 | PackageName string
28 |
29 | // Generate a complete source file, with package name and imports.
30 | Complete bool
31 |
32 | // Avoid generating the FuzzTest...(..., *testing.T) function.
33 | NoTestCase bool
34 |
35 | // Avoid generating the Fuzz...(..., *rand.Rand, uint)
36 | // function. This implies NoTestCase.
37 | NoDefaultFuzz bool
38 | }
39 |
40 | // Fuzzer is a pair of an interface declaration and a description of
41 | // how to generate the fuzzer.
42 | type Fuzzer struct {
43 | Name string
44 | Methods []Function
45 | Wanted WantedFuzzer
46 | }
47 |
48 | var (
49 | // Default generators for builtin types. If there is no entry
50 | // for the desired type, an error is signalled.
51 | defaultGenerators = map[string]string{
52 | "bool": "rand.Intn(2) == 0",
53 | "byte": "byte(rand.Uint32())",
54 | "complex64": "complex(float32(rand.NormFloat64()), float32(rand.NormFloat64()))",
55 | "complex128": "complex(rand.NormFloat64(), rand.NormFloat64())",
56 | "float32": "float32(rand.NormFloat64())",
57 | "float64": "rand.NormFloat64()",
58 | "int": "rand.Int()",
59 | "int8": "int8(rand.Int())",
60 | "int16": "int16(rand.Int())",
61 | "int32": "rand.Int31()",
62 | "int64": "rand.Int63()",
63 | "rune": "rune(rand.Int31())",
64 | "uint": "uint(rand.Uint32())",
65 | "uint8": "uint8(rand.Uint32())",
66 | "uint16": "uint16(rand.Uint32())",
67 | "uint32": "rand.Uint32()",
68 | "uint64": "(uint64(rand.Uint32()) << 32) | uint64(rand.Uint32())",
69 | }
70 |
71 | // Default comparisons for builtin types. If there is no entry
72 | // for the desired type, 'fallbackComparison' is used.
73 | defaultComparisons = map[string]string{
74 | "error": "((%s == nil) == (%s == nil))",
75 | }
76 |
77 | // Fallback comparison if there is nothing in 'defaultComparisons'.
78 | fallbackComparison = "reflect.DeepEqual(%s, %s)"
79 | )
80 |
81 | // All of the templates take a Fuzzer as the argument.
82 | const (
83 | // Template used by CodegenTestCase.
84 | testCaseTemplate = `
85 | {{$name := .Name}}
86 | {{$args := argV .Wanted.Reference.Parameters}}
87 |
88 | func FuzzTest{{$name}}(makeTest func({{$args}}) {{$name}}, t *testing.T) {
89 | rand := rand.New(rand.NewSource(0))
90 |
91 | err := Fuzz{{$name}}(makeTest, rand, 100)
92 |
93 | if err != nil {
94 | t.Error(err)
95 | }
96 | }`
97 |
98 | // Template used by CodegenWithDefaultReference
99 | withDefaultReferenceTemplate = `
100 | {{$name := .Name}}
101 | {{$args := argV .Wanted.Reference.Parameters}}
102 | {{$decls := makeFunCalls . .Wanted.Reference .Wanted.Reference.Name "makeTest"}}
103 | {{$and := eitherOr .Wanted.ReturnsValue "&" ""}}
104 |
105 | func Fuzz{{$name}}(makeTest func ({{$args}}) {{$name}}, rand *rand.Rand, max uint) error {
106 | {{indent $decls "\t"}}
107 |
108 | return Fuzz{{$name}}With({{$and}}expected{{$name}}, actual{{$name}}, rand, max)
109 | }`
110 |
111 | // Template used by CodegenWithReference
112 | withReferenceTemplate = `
113 | {{$fuzzer := .}}
114 | {{$name := .Name}}
115 | {{$count := len .Methods}}
116 | {{$state := .Wanted.GeneratorState}}
117 |
118 | func Fuzz{{$name}}With(reference {{$name}}, test {{$name}}, rand *rand.Rand, maxops uint) error {
119 | {{if $state | eq ""}}{{else}} // Create initial state
120 | state := {{$state}}
121 |
122 | {{end}} for i := uint(0); i < maxops; i++ {
123 | // Pick a random number between 0 and the number of methods of the interface. Then do that method on
124 | // both, check for discrepancy, and bail out on error. Simple!
125 |
126 | actionToPerform := rand.Intn({{$count}})
127 |
128 | switch actionToPerform { {{range $i, $function := .Methods}}
129 | case {{$i}}:
130 | // Call the method on both implementations
131 | {{indent (makeFunCalls $fuzzer $function (printf "reference.%s" $function.Name) (printf "test.%s" $function.Name)) "\t\t\t"}}
132 |
133 | // And check for discrepancies.{{range $j, $ty := $function.Returns}}{{$expected := expected $function $j}}{{$actual := actual $function $j}}
134 | if !{{printf (comparison $fuzzer $ty) $expected $actual}} {
135 | return fmt.Errorf("inconsistent result in {{$function.Name}}\nexpected: %v\nactual: %v", {{$expected}}, {{$actual}})
136 | }{{end}}{{end}}
137 | } {{range $i, $invariant := .Wanted.Invariants}}
138 |
139 | if !({{sed $invariant "%var" "reference"}}) {
140 | return errors.New("invariant violated: {{$invariant}}")
141 | }
142 | {{end}}
143 | }
144 |
145 | return nil
146 | }`
147 |
148 | // Template used by MakeFunctionCalls.
149 | functionCallTemplate = `
150 | {{$fuzzer := . }}
151 | {{$function := function ""}}
152 | {{$expecteds := expecteds $function}}
153 | {{$actuals := actuals $function}}
154 | {{$arguments := arguments $function}}
155 | {{$expectedFunc := expectedFunc ""}}
156 | {{$actualFunc := actualFunc ""}}
157 |
158 | {{if len $arguments | ne 0}}
159 | var ({{range $i, $ty := $function.Parameters}}
160 | {{argument $function $i}} {{toString $ty}}{{end}}
161 | )
162 | {{range $i, $ty := $function.Parameters}}
163 | {{makeTyGen $fuzzer (argument $function $i) $ty}}{{end}}{{end}}
164 |
165 | {{if len $expecteds | eq 0}}
166 | {{$expectedFunc}}({{varV $arguments}})
167 | {{$actualFunc}}({{varV $arguments}})
168 | {{else}}
169 | {{varV $expecteds}} := {{$expectedFunc}}({{varV $arguments}})
170 | {{varV $actuals}} := {{$actualFunc}}({{varV $arguments}})
171 | {{end}}`
172 | )
173 |
174 | /// ENTRY POINT
175 |
176 | // CodeGen generate code for the fuzzers.
177 | func CodeGen(options CodeGenOptions, imports []*ast.ImportSpec, fuzzers []Fuzzer) (string, []error) {
178 | var code string
179 | var errs []error
180 |
181 | if options.Complete {
182 | code = generatePreamble(options.PackageName, imports)
183 | }
184 |
185 | codeGenErr := func(fuzzer Fuzzer, err error) error {
186 | return fmt.Errorf("error occurred whilst generating code for '%s': %s", fuzzer.Name, err)
187 | }
188 |
189 | for _, fuzzer := range fuzzers {
190 | code = code + "// " + fuzzer.Name + "\n\n"
191 |
192 | // FuzzTest...(... *testing.T)
193 | if !(options.NoTestCase || options.NoDefaultFuzz) {
194 | generated, err := CodegenTestCase(fuzzer)
195 | if err != nil {
196 | errs = append(errs, codeGenErr(fuzzer, err))
197 | continue
198 | }
199 | code = code + generated + "\n\n"
200 | }
201 |
202 | // Fuzz...(... *rand.Rand, uint)
203 | if !options.NoDefaultFuzz {
204 | generated, err := CodegenWithDefaultReference(fuzzer)
205 | if err != nil {
206 | errs = append(errs, codeGenErr(fuzzer, err))
207 | continue
208 | }
209 | code = code + generated + "\n\n"
210 | }
211 |
212 | generated, err := CodegenWithReference(fuzzer)
213 | if err != nil {
214 | errs = append(errs, codeGenErr(fuzzer, err))
215 | continue
216 | }
217 | code = code + generated + "\n\n"
218 | }
219 |
220 | code, err := fixImports(options, code)
221 | if err != nil {
222 | errs = append(errs, err)
223 | }
224 |
225 | return code, errs
226 | }
227 |
228 | // Generates the header for a complete source file: the package name
229 | // and the imports. These imports may be both overzealous (all imports
230 | // from the source file are copied across) and incomplete (imports the
231 | // generated functions pull in aren't added), so the FixImports
232 | // function must be called after the full code has been generated to
233 | // fix this up.
234 | func generatePreamble(packagename string, imports []*ast.ImportSpec) string {
235 | preamble := "package " + packagename + "\n\n"
236 |
237 | for _, iport := range imports {
238 | preamble = preamble + generateImport(iport) + "\n"
239 | }
240 |
241 | return preamble + "\n"
242 | }
243 |
244 | // Generates an import statement from an *ast.ImportSpec.
245 | func generateImport(iport *ast.ImportSpec) string {
246 | if iport.Name != nil {
247 | return "import " + iport.Name.Name + " " + iport.Path.Value
248 | }
249 | return "import " + iport.Path.Value
250 | }
251 |
252 | // Adds and removes imports with 'goimports'.
253 | func fixImports(options CodeGenOptions, code string) (string, error) {
254 | if options.Complete {
255 | cbytes, err := goimports.Process(options.Filename, []byte(code), nil)
256 | return string(cbytes), err
257 | }
258 | return code, nil
259 | }
260 |
261 | /// MAIN CODE GENERATORS
262 |
263 | // CodegenTestCase generates a function which can be used as a test
264 | // case when given an implementation to test.
265 | //
266 | // For an interface named `Store` with a generating function that
267 | // takes a single `int`, the generated function signature looks like
268 | // this:
269 | //
270 | // FuzzTestStore(makeTest (func(int) Store), t *testing.T)
271 | //
272 | // This test case will call `FuzzStore` (see
273 | // CodegenWithDefaultReference) with a max number of 100 operations.
274 | func CodegenTestCase(fuzzer Fuzzer) (string, error) {
275 | return runTemplate("testCase", testCaseTemplate, fuzzer)
276 | }
277 |
278 | // CodegenWithDefaultReference generates a function which will compare
279 | // a supplied implementation of the interface against the reference,
280 | // by performing a sequence of random operations.
281 | //
282 | // For an interface named `Store` with a generating function that
283 | // takes a single `int`, the generated function signature looks like
284 | // this:
285 | //
286 | // FuzzStore(makeTest (func(int) Store), rand *rand.Rand, maxops uint) error
287 | //
288 | // This function will call `FuzzStoreWith` (see CodegenWithReference)
289 | // with the default reference.
290 | func CodegenWithDefaultReference(fuzzer Fuzzer) (string, error) {
291 | return runTemplate("withDefaultReference", withDefaultReferenceTemplate, fuzzer)
292 | }
293 |
294 | // CodegenWithReference generates a function which will compare two
295 | // arbitrary implementations of the supplied interface, by performing
296 | // a sequence of random operations.
297 | //
298 | // For an interface named `Store`, the generated function signature
299 | // looks like this:
300 | //
301 | // FuzzStoreWith(reference Store, test Store, rand *rand.Rand, maxops uint) error
302 | //
303 | // In any found discrepancies, the return value from the reference
304 | // `Store` (the first parameter) will be displayed as the "expected"
305 | // output, and the other as the "actual".
306 | func CodegenWithReference(fuzzer Fuzzer) (string, error) {
307 | return runTemplate("withReference", withReferenceTemplate, fuzzer)
308 | }
309 |
310 | /// FUNCTION CALLS
311 |
312 | // Generate a call to two functions with the same signature, with
313 | // random argument values.
314 | //
315 | // Arguments are stored in variables arg0 ... argN. Return values in
316 | // variables reta0 ... retaN and retb0 ... retbN.
317 | func makeFunctionCalls(fuzzer Fuzzer, function Function, funcA, funcB string) (string, error) {
318 | funcs := template.FuncMap{
319 | "function": func(s string) Function { return function },
320 | "expectedFunc": func(s string) string { return funcA },
321 | "actualFunc": func(s string) string { return funcB },
322 | }
323 |
324 | return runTemplateWith("functionCall", functionCallTemplate, fuzzer, funcs)
325 | }
326 |
327 | /// VALUE INITIALISATION
328 |
329 | // Produce some code to populate a given variable with a random value
330 | // of the named type, assuming a PRNG called 'rand' is in scope.
331 | func makeTypeGenerator(fuzzer Fuzzer, varname string, ty Type) (string, error) {
332 | tyname := ty.ToString()
333 |
334 | // If there's a provided generator, use that.
335 | generator, ok := fuzzer.Wanted.Generator[tyname]
336 | if ok {
337 | if generator.IsStateful {
338 | if fuzzer.Wanted.GeneratorState == "" {
339 | return "", errors.New("stateful generator used when no initial state given")
340 | }
341 | return fmt.Sprintf("%s, state = %s(rand, state)", varname, generator.Name), nil
342 | }
343 | return fmt.Sprintf("%s = %s(rand)", varname, generator.Name), nil
344 | }
345 |
346 | // If it's a type we can handle, supply a default generator.
347 | var tygen string
348 | tygen, ok = defaultGenerators[tyname]
349 | if ok {
350 | return fmt.Sprintf("%s = %s", varname, tygen), nil
351 | }
352 |
353 | // Otherwise cry because generic programming in Go is hard :(
354 | return "", fmt.Errorf("I don't know how to generate a %s", tyname)
355 | }
356 |
357 | /// VALUE COMPARISON
358 |
359 | // Produce a format string to compare two values of the same type.
360 | // given the variable names.
361 | func makeValueComparison(fuzzer Fuzzer, ty Type) string {
362 | tyname := ty.ToString()
363 | comparison, ok := defaultComparisons[tyname]
364 | if !ok {
365 | comparison = fallbackComparison
366 | }
367 |
368 | // If there's a provided comparison, use that.
369 | tycomp, ok := fuzzer.Wanted.Comparison[tyname]
370 | if ok {
371 | comparison = "%s." + tycomp.Name + "(%s)"
372 |
373 | if tycomp.IsFunction {
374 | comparison = tycomp.Name + "(%s, %s)"
375 | }
376 | }
377 |
378 | return comparison
379 | }
380 |
381 | /// TEMPLATES
382 |
383 | // Run a template and return the output.
384 | func runTemplate(tplName, tpl string, fuzzer Fuzzer) (string, error) {
385 | return runTemplateWith(tplName, tpl, fuzzer, nil)
386 | }
387 |
388 | // Run a template and return the output, overriding the built-in
389 | // template functions with a custom map (which can also add new
390 | // functions).
391 | func runTemplateWith(tplName, tpl string, fuzzer Fuzzer, overrides template.FuncMap) (string, error) {
392 | funcMap := template.FuncMap{
393 | // Render a list of types
394 | "argV": func(types []Type) string {
395 | var args []string
396 |
397 | for _, ty := range types {
398 | args = append(args, ty.ToString())
399 | }
400 |
401 | return strings.Join(args, ", ")
402 | },
403 | // Render a list of variables
404 | "varV": func(vars []string) string {
405 | return strings.Join(vars, ", ")
406 | },
407 | // Select one of two values based on a flag
408 | "eitherOr": func(f bool, a, b string) string {
409 | if f {
410 | return a
411 | }
412 | return b
413 | },
414 | // Indent every line of a string
415 | "indent": indentLines,
416 | // Argument names
417 | "arguments": func(function Function) []string {
418 | return funcArgNames(function)
419 | },
420 | "argument": func(function Function, i int) (string, error) {
421 | return inSlice(funcArgNames(function), i, "argument")
422 | },
423 | // Expected value names
424 | "expecteds": func(function Function) []string { return funcExpectedNames(function) },
425 | "expected": func(function Function, i int) (string, error) {
426 | return inSlice(funcExpectedNames(function), i, "result")
427 | },
428 | // Actual value names
429 | "actuals": func(function Function) []string {
430 | return funcActualNames(function)
431 | },
432 | "actual": func(function Function, i int) (string, error) {
433 | return inSlice(funcActualNames(function), i, "result")
434 | },
435 | // Render a type as a string
436 | "toString": func(ty Type) string {
437 | return ty.ToString()
438 | },
439 | // Make a function call
440 | "makeFunCalls": makeFunctionCalls,
441 | // Make a value comparison
442 | "comparison": makeValueComparison,
443 | // Make a type generator
444 | "makeTyGen": makeTypeGenerator,
445 | // Replace one string with another
446 | "sed": func(s, old, new string) string {
447 | return strings.Replace(s, old, new, -1)
448 | },
449 | }
450 |
451 | for k, v := range overrides {
452 | funcMap[k] = v
453 | }
454 |
455 | var buf bytes.Buffer
456 | t, err := template.New(tplName).Funcs(funcMap).Parse(tpl)
457 | if err != nil {
458 | return "", err
459 | }
460 | err = t.Execute(&buf, fuzzer)
461 | return strings.TrimSpace(string(buf.Bytes())), err
462 | }
463 |
464 | // Safe slice lookup
465 | func inSlice(ss []string, i int, name string) (string, error) {
466 | if i < 0 || i >= len(ss) {
467 | return "", errors.New(name + " index out of range")
468 | }
469 |
470 | return ss[i], nil
471 | }
472 |
473 | /// TYPE-DIRECTED VARIABLE NAMING
474 |
475 | // Produce unique variable names for function arguments. These do not
476 | // clash with names produced by funcExpectedNames or funcActualNames.
477 | func funcArgNames(function Function) []string {
478 | return typeListNames("arg", function.Parameters)
479 | }
480 |
481 | // Produce unique variable names for actual function returns. These do
482 | // not clash with names produced by funcArgNames or funcExpectedNames.
483 | func funcActualNames(function Function) []string {
484 | return typeListNames("actual", function.Returns)
485 | }
486 |
487 | // Produce unique variable names for expected function returns. These
488 | // do not clash with names produced by funcArgNames or
489 | // funcActualNames.
490 | func funcExpectedNames(function Function) []string {
491 | return typeListNames("expected", function.Returns)
492 | }
493 |
494 | // Produce names for variables given a list of types.
495 | func typeListNames(prefix string, tylist []Type) []string {
496 | var names []string
497 |
498 | for i, ty := range tylist {
499 | // Generate a name for this variable based on the type.
500 | name := typeNameToVarName(prefix, ty)
501 | for _, prior := range names {
502 | if name == prior {
503 | name = name + strconv.Itoa(i)
504 | break
505 | }
506 | }
507 | names = append(names, name)
508 | }
509 |
510 | return names
511 | }
512 |
513 | // Produce a (possibly not unique) variable name from a type name.
514 | func typeNameToVarName(pref string, ty Type) string {
515 | name := filter(ty.ToString(), unicode.IsLetter)
516 |
517 | // More pleasing capitalisation.
518 | for i, r := range name {
519 | name = string(unicode.ToUpper(r)) + name[i+1:]
520 | break
521 | }
522 |
523 | return pref + name
524 | }
525 |
--------------------------------------------------------------------------------
/interface.go:
--------------------------------------------------------------------------------
1 | // Parse interface declarations.
2 | //
3 | // For all of the InterfaceFrom* functions, if the parameter is not an
4 | // interface type, the default interface is returned and the error is
5 | // non-nil. Similarly for the FunctionFrom* functions.
6 |
7 | package main
8 |
9 | import (
10 | "fmt"
11 | "go/ast"
12 | )
13 |
14 | // A Function is a representation of a function name and type, which
15 | // may be a member of an interface or not. All Functions in an
16 | // Interface will be members.
17 | type Function struct {
18 | // The name of the function.
19 | Name string
20 |
21 | // The parameter types
22 | Parameters []Type
23 |
24 | // The output types
25 | Returns []Type
26 | }
27 |
28 | // Type is a representation of a Go type. The concrete types are
29 | // ArrayType, BasicType, ChanType, MapType, PointerType, and
30 | // QualifiedType.
31 | type Type interface {
32 | // Return an unambiguous string rendition of the type.
33 | ToString() string
34 | }
35 |
36 | // ArrayType is the type of arrays.
37 | type ArrayType struct {
38 | // The element type
39 | ElementType Type
40 | }
41 |
42 | // ToString converts an ArrayType into a string of the form
43 | // "[](type)".
44 | func (ty *ArrayType) ToString() string {
45 | if ty == nil {
46 | return ""
47 | }
48 |
49 | tystr := fmt.Sprintf("[](%s)", ty.ElementType.ToString())
50 | return tystr
51 | }
52 |
53 | // BasicType is simple named types with no additional structure.
54 | type BasicType string
55 |
56 | // ToString just exposes the underlying type name.
57 | func (ty *BasicType) ToString() string {
58 | if ty == nil {
59 | return ""
60 | }
61 |
62 | return string(*ty)
63 | }
64 |
65 | // ChanType is the type of channels.
66 | type ChanType struct {
67 | // The element type.
68 | ElementType Type
69 | }
70 |
71 | // ToString converts a ChanType into a string of the form "chan
72 | // (type)".
73 | func (ty *ChanType) ToString() string {
74 | if ty == nil {
75 | return ""
76 | }
77 |
78 | tystr := fmt.Sprintf("chan (%s)", ty.ElementType.ToString())
79 | return tystr
80 | }
81 |
82 | // MapType is the type of maps.
83 | type MapType struct {
84 | // The key type
85 | KeyType Type
86 | // The value type.
87 | ValueType Type
88 | }
89 |
90 | // ToString converts a MapType into a string of the form
91 | // "map[type](type)".
92 | func (ty *MapType) ToString() string {
93 | if ty == nil {
94 | return ""
95 | }
96 |
97 | tystr := fmt.Sprintf("map[%s](%s)", ty.KeyType.ToString(), ty.ValueType.ToString())
98 | return tystr
99 | }
100 |
101 | // PointerType is the type of pointers.
102 | type PointerType struct {
103 | // The target type.
104 | TargetType Type
105 | }
106 |
107 | // QualifiedType is the type of types with a package name qualfiier.
108 | type QualifiedType struct {
109 | // The package name
110 | Package string
111 | // The type
112 | Type Type
113 | }
114 |
115 | // ToString converts a QualifiedType into a string of the form
116 | // "package.type".
117 | func (ty *QualifiedType) ToString() string {
118 | if ty == nil {
119 | return ""
120 | }
121 |
122 | tystr := fmt.Sprintf("%s.%s", ty.Package, ty.Type.ToString())
123 | return tystr
124 | }
125 |
126 | // ToString converts a PointerType into a string of the form
127 | // "*(type)".
128 | func (ty *PointerType) ToString() string {
129 | if ty == nil {
130 | return ""
131 | }
132 |
133 | tystr := fmt.Sprintf("*(%s)", ty.TargetType.ToString())
134 | return tystr
135 | }
136 |
137 | // InterfacesFromAST extracts all interface declarations from the AST
138 | // of a file, as a map from names to interface decls.
139 | func InterfacesFromAST(theAST ast.Node) map[string][]Function {
140 | if theAST == nil {
141 | return nil
142 | }
143 |
144 | interfaces := make(map[string][]Function)
145 |
146 | ast.Inspect(theAST, func(node ast.Node) bool {
147 | switch tyspec := node.(type) {
148 | case *ast.TypeSpec:
149 | name := tyspec.Name.Name
150 | switch ifacety := tyspec.Type.(type) {
151 | case *ast.InterfaceType:
152 | functions, err := FunctionsFromInterfaceType(*ifacety)
153 | if err == nil {
154 | interfaces[name] = functions
155 | }
156 | }
157 |
158 | // Whether we found one or not, there can be
159 | // no interfaces at a lower level than this.
160 | return false
161 | }
162 |
163 | // Maybe we just haven't recursed deeply
164 | // enough. Onwards!
165 | return true
166 | })
167 |
168 | return interfaces
169 | }
170 |
171 | // FunctionsFromInterfaceType tries to extract function declarations
172 | // from an ast.InterfaceType.
173 | func FunctionsFromInterfaceType(ifacety ast.InterfaceType) ([]Function, error) {
174 | var functions []Function
175 | for _, field := range ifacety.Methods.List {
176 | if len(field.Names) == 0 {
177 | continue
178 | }
179 |
180 | // Can there be more than one name?
181 | name := field.Names[0].Name
182 | obj := field.Names[0].Obj
183 |
184 | if obj == nil || obj.Decl == nil {
185 | continue
186 | }
187 |
188 | switch fundecl := obj.Decl.(type) {
189 | case *ast.Field:
190 | ast.Inspect(fundecl, func(node ast.Node) bool {
191 | switch funty := node.(type) {
192 | case *ast.FuncType:
193 | function := Function{Name: name}
194 | if funty.Params != nil {
195 | function.Parameters = TypeListFromFieldList(*funty.Params)
196 | }
197 | if funty.Results != nil {
198 | function.Returns = TypeListFromFieldList(*funty.Results)
199 | }
200 | functions = append(functions, function)
201 | return false
202 | }
203 |
204 | return true
205 | })
206 | }
207 | }
208 |
209 | return functions, nil
210 | }
211 |
212 | // TypeListFromFieldList gets the list of type names from an
213 | // ast.FieldList. Names are not returned.
214 | func TypeListFromFieldList(fields ast.FieldList) []Type {
215 | types := make([]Type, len(fields.List))
216 |
217 | for i, field := range fields.List {
218 | types[i] = TypeFromTypeExpr(field.Type)
219 | }
220 |
221 | return types
222 | }
223 |
224 | // TypeFromTypeExpr gets a type from an ast.Expr which is known to
225 | // represent a type.
226 | func TypeFromTypeExpr(ty ast.Expr) Type {
227 | switch x := ty.(type) {
228 | case *ast.Ident:
229 | // Type name
230 | ty := BasicType(x.Name)
231 | return &ty
232 | case *ast.ArrayType:
233 | ty := ArrayType{ElementType: TypeFromTypeExpr(x.Elt)}
234 | return &ty
235 | case *ast.ChanType:
236 | ty := ChanType{ElementType: TypeFromTypeExpr(x.Value)}
237 | return &ty
238 | case *ast.MapType:
239 | ty := MapType{KeyType: TypeFromTypeExpr(x.Key), ValueType: TypeFromTypeExpr(x.Value)}
240 | return &ty
241 | case *ast.StarExpr:
242 | ty := PointerType{TargetType: TypeFromTypeExpr(x.X)}
243 | return &ty
244 | case *ast.SelectorExpr:
245 | // x.X is an expression which resolves to the package
246 | // name and x.Sel is the "selector", which is the
247 | // actual type name.
248 | pkg := TypeFromTypeExpr(x.X).ToString()
249 | innerTy := BasicType(x.Sel.Name)
250 | ty := QualifiedType{Package: pkg, Type: &innerTy}
251 | return &ty
252 | }
253 |
254 | return nil
255 | }
256 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "go/parser"
6 | "go/token"
7 | "io/ioutil"
8 | "os"
9 | "strings"
10 |
11 | "github.com/urfave/cli"
12 | )
13 |
14 | // Turn a collection of errors into a single error message with a list
15 | // of errors.
16 | func errorList(message string, errs []error) string {
17 | var errstrs []string
18 | for _, err := range errs {
19 | errstrs = append(errstrs, err.Error())
20 | }
21 | return (message + ":\n\t- " + strings.Join(errstrs, "\n\t- "))
22 | }
23 |
24 | // Reconcile the wanted fuzzers with the interfaces. Complain if there
25 | // are any wanted fuzzers for which the interface decl isn't in the
26 | // file.
27 | func reconcileFuzzers(interfaces map[string][]Function, wanteds []WantedFuzzer) ([]Fuzzer, []error) {
28 | var errs []error
29 |
30 | // Fuzzers are stored as a map from interface name to fuzzer.
31 | // This allows rapid checking for duplicates.
32 | fuzzers := make(map[string]Fuzzer)
33 |
34 | for _, wanted := range wanteds {
35 | _, present := fuzzers[wanted.InterfaceName]
36 | if present {
37 | errs = append(errs, fmt.Errorf("already have a fuzzer for '%s'", wanted.InterfaceName))
38 | continue
39 | }
40 |
41 | methods, ok := interfaces[wanted.InterfaceName]
42 |
43 | if !ok {
44 | errs = append(errs, fmt.Errorf("couldn't find interface '%s' in this file", wanted.InterfaceName))
45 | }
46 |
47 | fuzzer := Fuzzer{Name: wanted.InterfaceName, Methods: methods, Wanted: wanted}
48 | fuzzers[wanted.InterfaceName] = fuzzer
49 | }
50 |
51 | // Get a slice out of the 'fuzzers' map
52 | realfuzzers := make([]Fuzzer, len(fuzzers))
53 | i := 0
54 | for _, fuzzer := range fuzzers {
55 | realfuzzers[i] = fuzzer
56 | i++
57 | }
58 | return realfuzzers, errs
59 | }
60 |
61 | func main() {
62 | var opts CodeGenOptions
63 | var ifaceonly string
64 | var writeout bool
65 |
66 | app := cli.NewApp()
67 | app.Name = "go-interface-fuzzer"
68 | app.Usage = "Generate fuzz tests for Go interfaces."
69 | app.Flags = []cli.Flag{
70 | cli.BoolFlag{
71 | Name: "complete, c",
72 | Usage: "Generate a complete source file, with package name and imports",
73 | Destination: &opts.Complete,
74 | },
75 | cli.StringFlag{
76 | Name: "filename, f",
77 | Usage: "Use `FILE` as the file name when automatically resolving imports (defaults to the filename of the source file)",
78 | Destination: &opts.Filename,
79 | },
80 | cli.StringFlag{
81 | Name: "package, p",
82 | Usage: "Use `NAME` as the package name (defaults to the package of the source file)",
83 | Destination: &opts.PackageName,
84 | },
85 | cli.BoolFlag{
86 | Name: "no-test-case, T",
87 | Usage: "Do not generate the TestFuzz... function",
88 | Destination: &opts.NoTestCase,
89 | },
90 | cli.BoolFlag{
91 | Name: "no-default, D",
92 | Usage: "Do not generate the Fuzz... function, implies no-test-case",
93 | Destination: &opts.NoDefaultFuzz,
94 | },
95 | cli.StringFlag{
96 | Name: "interface",
97 | Usage: "Ignore special comments and just generate a fuzz tester for the named interface, implies no-default",
98 | Destination: &ifaceonly,
99 | },
100 | cli.BoolFlag{
101 | Name: "output, o",
102 | Usage: "Write the output to the filename given by the -f flag, which must be specified",
103 | Destination: &writeout,
104 | },
105 | }
106 | app.Action = func(c *cli.Context) error {
107 | if len(c.Args()) < 1 {
108 | return cli.NewExitError("Must specify a file to generate a fuzzer from.", 1)
109 | }
110 |
111 | filename := c.Args().Get(0)
112 | fset := token.NewFileSet()
113 | parsedFile, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
114 |
115 | if err != nil {
116 | return cli.NewExitError(fmt.Sprintf("Could not parse file: '%s'", err.Error()), 1)
117 | }
118 |
119 | // Extract all the interfaces
120 | interfaces := InterfacesFromAST(parsedFile)
121 |
122 | // Extract the wanted fuzzers
123 | var wanteds []WantedFuzzer
124 | var werrs []error
125 | if ifaceonly == "" {
126 | wanteds, werrs = WantedFuzzersFromAST(parsedFile)
127 | } else {
128 | // Default fuzzer for this interface.
129 | wanteds = append(wanteds, WantedFuzzer{InterfaceName: ifaceonly})
130 | }
131 | if len(werrs) > 0 {
132 | return cli.NewExitError(errorList("Found errors while extracting interface definitions", werrs), 1)
133 | }
134 |
135 | // Reconcile the wanteds with the interfaces.
136 | fuzzers, ferrs := reconcileFuzzers(interfaces, wanteds)
137 | if len(ferrs) > 0 {
138 | return cli.NewExitError(errorList("Found errors while determining wanted fuzz testers", ferrs), 1)
139 | }
140 |
141 | // Codegen
142 | if opts.Filename == "" {
143 | if writeout {
144 | return cli.NewExitError("When using -o a filename MUST be given to -f", 1)
145 | }
146 | opts.Filename = filename
147 | }
148 | if opts.PackageName == "" {
149 | opts.PackageName = parsedFile.Name.Name
150 | }
151 | code, cerrs := CodeGen(opts, parsedFile.Imports, fuzzers)
152 | if len(cerrs) > 0 {
153 | return cli.NewExitError(errorList("Found some errors while generating code", cerrs), 1)
154 | }
155 |
156 | if writeout {
157 | err := ioutil.WriteFile(opts.Filename, []byte(code), 0644)
158 | if err != nil {
159 | return cli.NewExitError(err.Error(), 1)
160 | }
161 | } else {
162 | fmt.Println(code)
163 | }
164 |
165 | return nil
166 | }
167 |
168 | app.Run(os.Args)
169 | }
170 |
--------------------------------------------------------------------------------
/media/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pusher/go-interface-fuzzer/cebf347f37c499000edc0ccf4093adab119cbc8a/media/logo.png
--------------------------------------------------------------------------------
/strings.go:
--------------------------------------------------------------------------------
1 | // String operations used elsewhere in the parser.
2 |
3 | package main
4 |
5 | import (
6 | "strings"
7 | "unicode"
8 | )
9 |
10 | // Drop a prefix and suffix. Complain if the prefix is present but not
11 | // the suffix and vice versa.
12 | func matchDelims(s, pref, suff string) (string, bool) {
13 | s1, prefCount := removePrefix(s, pref)
14 | s2, suffCount := removeSuffix(s1, suff)
15 |
16 | return s2, prefCount == suffCount
17 | }
18 |
19 | // Remove a prefix from a string as many times as possible.
20 | func removePrefix(s, pref string) (string, uint) {
21 | var count uint
22 | for strings.HasPrefix(s, pref) {
23 | s = strings.TrimPrefix(s, pref)
24 | count++
25 | }
26 | return s, count
27 | }
28 |
29 | // Remove a suffix from a string as many times as possible.
30 | func removeSuffix(s, suff string) (string, uint) {
31 | var count uint
32 | for strings.HasSuffix(s, suff) {
33 | s = strings.TrimSuffix(s, suff)
34 | count++
35 | }
36 | return s, count
37 | }
38 |
39 | // Split a string into lines.
40 | func splitLines(s string) []string {
41 | return strings.Split(s, "\n")
42 | }
43 |
44 | // Check if a string has a prefix and, if so, return the bit following
45 | // the prefix with whitespace betwen the prefix and the rest trimmed.
46 | func matchPrefix(s, prefix string) (string, bool) {
47 | if strings.HasPrefix(s, prefix) {
48 | after := strings.TrimPrefix(s, prefix)
49 | return strings.TrimLeftFunc(after, unicode.IsSpace), true
50 | }
51 | return s, false
52 | }
53 |
54 | // Return a prefix and a suffix where the prefix contains only allowed
55 | // characters.
56 | func takeWhileIn(s, allowed string) (string, string) {
57 | for i, chr := range s {
58 | if !strings.ContainsRune(allowed, chr) {
59 | return s[:i], s[i:]
60 | }
61 | }
62 |
63 | return s, ""
64 | }
65 |
66 | // Indent every non-blank line by the given number of tabs.
67 | func indentLines(s string, indent string) string {
68 | lines := strings.Split(s, "\n")
69 | indented := indent + strings.Join(lines, "\n"+indent)
70 | return strings.Replace(indented, indent+"\n", "\n", -1)
71 | }
72 |
73 | // Filter runes in a string. Unlike takeWhileIn this processes the
74 | // entire string, not just a prefix.
75 | func filter(s string, allowed func(rune) bool) string {
76 | f := func(r rune) rune {
77 | if allowed(r) {
78 | return r
79 | }
80 | return -1
81 | }
82 |
83 | return strings.Map(f, s)
84 | }
85 |
--------------------------------------------------------------------------------
/strings_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 | "testing/quick"
8 | "unicode"
9 | )
10 |
11 | // Check that matchDelims can remove delimiters and get back the
12 | // original string.
13 | func TestMatchDelims(t *testing.T) {
14 | f := func(str string, numL uint, numR uint) bool {
15 | numL = numL % 10
16 | numR = numR % 10
17 |
18 | pref := makeFixedString("(", numL)
19 | suff := makeFixedString(")", numR)
20 |
21 | teststr := fmt.Sprintf("%s|%s|%s", pref, str, suff)
22 |
23 | innerstr, balanced := matchDelims(teststr, "(", ")")
24 |
25 | if innerstr != "|"+str+"|" {
26 | expectedActual("Failed at matching delimiters.", "|"+str+"|", innerstr, t)
27 | }
28 |
29 | if (balanced && numL != numR) || (!balanced && numL == numR) {
30 | t.Fatal("Reported delimiters balanced when they are not.")
31 | }
32 |
33 | return true
34 | }
35 |
36 | quickcheck(f, t)
37 | }
38 |
39 | // Check that the result of splitLines can be converted back to the
40 | // original string.
41 | func TestSplitLinesInvertible(t *testing.T) {
42 | f := func(str string) bool {
43 | splitted := splitLines(str)
44 | merged := strings.Join(splitted, "\n")
45 | if str != merged {
46 | expectedActual("Splitting a string by lines is not invertible.", str, merged, t)
47 | }
48 |
49 | return true
50 | }
51 |
52 | quickcheck(f, t)
53 | }
54 |
55 | // Check that the result of splitLines doesn't contain newlines.
56 | func TestSplitLinesNoNewlines(t *testing.T) {
57 | f := func(str string) bool {
58 | splitted := splitLines(str)
59 | for _, line := range splitted {
60 | if strings.Contains(line, "\n") {
61 | chunks := strings.Split(line, "\n")
62 | expectedActual("Newline found in output of string split by lines.", chunks[0], line, t)
63 | }
64 | }
65 |
66 | return true
67 | }
68 |
69 | quickcheck(f, t)
70 | }
71 |
72 | // Check matchPrefix correctly removes the prefix.
73 | func TestMatchPrefix(t *testing.T) {
74 | f := func(pref, suff string) bool {
75 | str := pref + suff
76 |
77 | suff2, ok := matchPrefix(str, pref)
78 |
79 | if !ok || suff2 != suff {
80 | expectedActual("Failed to match prefix.", suff, suff2, t)
81 | }
82 |
83 | return true
84 | }
85 |
86 | quickcheck(f, t)
87 | }
88 |
89 | // Check that takeWhileIn returns a prefix of the original string.
90 | func TestTakeWhileInGivesPrefix(t *testing.T) {
91 | f := func(str, allowed string) bool {
92 | pref, suff := takeWhileIn(str, allowed)
93 |
94 | suff2, ok := matchPrefix(str, pref)
95 |
96 | if !ok || suff != suff2 {
97 | expectedActual("Failed to match prefix given by takeWhileIn.", suff2, suff, t)
98 | }
99 |
100 | return true
101 | }
102 |
103 | quickcheck(f, t)
104 | }
105 |
106 | // Check that takeWhileIn returns a prefix which only contains allowed characters.
107 | func TestTakeWhileInGivesGoodPrefix(t *testing.T) {
108 | f := func(str, allowed string) bool {
109 | pref, _ := takeWhileIn(str, allowed)
110 |
111 | for _, chr := range pref {
112 | if !strings.ContainsRune(allowed, chr) {
113 | t.Fatal("Found invalid character in prefix.")
114 | }
115 | }
116 |
117 | return true
118 | }
119 |
120 | quickcheck(f, t)
121 | }
122 |
123 | // Check that filter doesn't increase the length.
124 | func TestFilterLen(t *testing.T) {
125 | f := func(str string) bool {
126 | return len(str) >= len(filter(str, unicode.IsLetter))
127 | }
128 |
129 | quickcheck(f, t)
130 | }
131 |
132 | // Check that the result of filter only contains characters matching
133 | // the predicate.
134 | func TestFilterPredicate(t *testing.T) {
135 | f := func(str string) bool {
136 | predicate := unicode.IsLetter
137 | filtered := filter(str, predicate)
138 |
139 | for _, r := range filtered {
140 | if !predicate(r) {
141 | t.Fatal("Found invalid character in filter output.")
142 | }
143 | }
144 |
145 | return true
146 | }
147 |
148 | quickcheck(f, t)
149 | }
150 |
151 | // Check that filtering is idempotent.
152 | func TestFilterIdempotent(t *testing.T) {
153 | f := func(str string) bool {
154 | predicate := unicode.IsLetter
155 | filtered1 := filter(str, predicate)
156 | filtered2 := filter(filtered1, predicate)
157 |
158 | if filtered1 != filtered2 {
159 | expectedActual("Filtering is not idempotent.", filtered1, filtered2, t)
160 | }
161 |
162 | return true
163 | }
164 |
165 | quickcheck(f, t)
166 | }
167 |
168 | // Helper for "expected"/"actual" output.
169 | func expectedActual(msg string, expected interface{}, actual interface{}, t *testing.T) {
170 | t.Fatal(fmt.Sprintf("%s\nGot: %v\nExpected: %v", msg, actual, expected))
171 | }
172 |
173 | // Helper for quickcheck
174 | func quickcheck(f interface{}, t *testing.T) {
175 | var c quick.Config
176 | c.MaxCount = 10
177 | if err := quick.Check(f, &c); err != nil {
178 | t.Fatal(err)
179 | }
180 | }
181 |
182 | // Helper for making strings of a single character.
183 | func makeFixedString(of string, len uint) (s string) {
184 | var i uint
185 | for i = 0; i < len; i++ {
186 | s = s + of
187 | }
188 | return
189 | }
190 |
--------------------------------------------------------------------------------
/wanted.go:
--------------------------------------------------------------------------------
1 | // Parse fuzzer special comments.
2 | //
3 | // For all the WantedFuzzerFrom* functions, if the parameter does not
4 | // contain a fuzzer special comment, the default wanted fuzzer is
5 | // returned and the error is non-nil.
6 | //
7 | // See the README for a comprehensive explanation of the special
8 | // comments.
9 |
10 | package main
11 |
12 | import (
13 | "errors"
14 | "fmt"
15 | "go/ast"
16 | "strings"
17 | "unicode"
18 | )
19 |
20 | // WantedFuzzer is a description of a fuzzer we want to generate.
21 | type WantedFuzzer struct {
22 | // The name of the interface.
23 | InterfaceName string
24 |
25 | // The function to produce a reference implementation.
26 | Reference Function
27 |
28 | // If true, the reference function returns a value rather than a
29 | // pointer.
30 | ReturnsValue bool
31 |
32 | // Invariant expressions.
33 | Invariants []string
34 |
35 | // Comparison functions to use. The keys of this map are
36 | // ToString'd Types.
37 | Comparison map[string]EitherFunctionOrMethod
38 |
39 | // Generator functions The keys of this map are ToString'd Types.
40 | Generator map[string]Generator
41 |
42 | // Initial state for custom generator functions.
43 | GeneratorState string
44 | }
45 |
46 | // Generator is the name of a function to generate a value of a given
47 | // type.
48 | type Generator struct {
49 | // True if this is stateful.
50 | IsStateful bool
51 |
52 | // The function itself.
53 | Name string
54 | }
55 |
56 | // EitherFunctionOrMethod is either a function or a method. Param and
57 | // receiver types are all the same.
58 | type EitherFunctionOrMethod struct {
59 | // True if this is function, rather than a method.
60 | IsFunction bool
61 |
62 | // The function itself.
63 | Name string
64 |
65 | // The type of the method receiver / function parameters.
66 | Type Type
67 |
68 | // List of return types. Only meaningful in "@before compare".
69 | Returns []Type
70 | }
71 |
72 | // WantedFuzzersFromAST extracts all wanted fuzzers from comments in
73 | // the AST of a file.
74 | func WantedFuzzersFromAST(theAST *ast.File) (wanteds []WantedFuzzer, errs []error) {
75 | if theAST == nil {
76 | return nil, nil
77 | }
78 |
79 | if theAST.Doc != nil {
80 | wanted, err := WantedFuzzersFromCommentGroup(theAST.Doc)
81 | if err == nil {
82 | wanteds = append(wanteds, wanted...)
83 | } else {
84 | errs = append(errs, err)
85 | }
86 | }
87 |
88 | for _, group := range theAST.Comments {
89 | wanted, err := WantedFuzzersFromCommentGroup(group)
90 | if err == nil {
91 | wanteds = append(wanteds, wanted...)
92 | } else {
93 | errs = append(errs, err)
94 | }
95 | }
96 |
97 | return wanteds, errs
98 | }
99 |
100 | // WantedFuzzersFromCommentGroup extracts all wanted fuzzer
101 | // descriptions from a comment group. The "@fuzz interface:" line
102 | // starts a new fuzzer definition; special comments in a group before
103 | // this are ignored.
104 | func WantedFuzzersFromCommentGroup(group *ast.CommentGroup) ([]WantedFuzzer, error) {
105 | if group == nil {
106 | return nil, nil
107 | }
108 |
109 | var commentLines []string
110 | for _, comment := range group.List {
111 | lines := splitLines(comment.Text)
112 | commentLines = append(commentLines, lines...)
113 | }
114 |
115 | return WantedFuzzersFromCommentLines(commentLines)
116 | }
117 |
118 | // WantedFuzzersFromCommentLines extracts all wanted fuzzer
119 | // descriptions from a collection of comment lines. The "@fuzz
120 | // interface:" line starts a new fuzzer definition; special comments
121 | // in a group before this are ignored.
122 | func WantedFuzzersFromCommentLines(commentLines []string) ([]WantedFuzzer, error) {
123 | if commentLines == nil {
124 | return nil, nil
125 | }
126 |
127 | var fuzzers []WantedFuzzer
128 | var fuzzer WantedFuzzer
129 |
130 | // 'fuzzing' indicates whether we've found the start of a
131 | // special comment or not. If not, just look for "@fuzz
132 | // interface" and ignore everything else.
133 | fuzzing := false
134 |
135 | for _, line := range commentLines {
136 | line = strings.TrimSpace(line)
137 |
138 | var err error
139 | if fuzzing {
140 | err = parseLine(line, &fuzzer)
141 | } else {
142 | // "@fuzz interface:"
143 | suff, ok := matchPrefix(line, "@fuzz interface:")
144 | if !ok {
145 | continue
146 | }
147 |
148 | if fuzzing {
149 | // Found a new fuzzer! Add the old one to the list.
150 | if fuzzer.Reference.Name == "" {
151 | return fuzzers, fmt.Errorf("fuzzer declaration for %s missing '@known correct' line", fuzzer.InterfaceName)
152 | }
153 | fuzzers = append(fuzzers, fuzzer)
154 |
155 | }
156 |
157 | var name string
158 | name, err = parseFuzzInterface(suff)
159 | fuzzer = WantedFuzzer{
160 | InterfaceName: name,
161 | Comparison: make(map[string]EitherFunctionOrMethod),
162 | Generator: make(map[string]Generator),
163 | }
164 | fuzzing = true
165 | }
166 |
167 | if err != nil {
168 | return fuzzers, err
169 | }
170 |
171 | }
172 |
173 | if fuzzing {
174 | // Add the final fuzzer to the list.
175 | if fuzzer.Reference.Name == "" {
176 | return fuzzers, fmt.Errorf("fuzzer declaration for %s missing '@known correct' line", fuzzer.InterfaceName)
177 | }
178 | return append(fuzzers, fuzzer), nil
179 | }
180 |
181 | return fuzzers, nil
182 | }
183 |
184 | /* Parse a line in a comment. If this is a special comment, handle it
185 | and mutate the wanted fuzzer; if not, skip over.
186 |
187 | SYNTAX: @known correct:
188 | | @invariant:
189 | | @comparison:
190 | | @generator:
191 | | @generator state:
192 | */
193 | func parseLine(line string, fuzzer *WantedFuzzer) error {
194 | // "@known correct:"
195 | suff, ok := matchPrefix(line, "@known correct:")
196 | if ok {
197 | fundecl, returnsValue, err := parseKnownCorrect(suff)
198 | if err != nil {
199 | return err
200 | }
201 |
202 | retty := BasicType(fuzzer.InterfaceName)
203 | fundecl.Returns = []Type{&retty}
204 | fuzzer.Reference = fundecl
205 | fuzzer.ReturnsValue = returnsValue
206 | }
207 |
208 | // "@invariant:"
209 | suff, ok = matchPrefix(line, "@invariant:")
210 | if ok {
211 | inv, err := parseInvariant(suff)
212 | if err != nil {
213 | return err
214 | }
215 |
216 | fuzzer.Invariants = append(fuzzer.Invariants, inv)
217 | }
218 |
219 | // "@comparison:"
220 | suff, ok = matchPrefix(line, "@comparison:")
221 | if ok {
222 | tyname, fundecl, err := parseComparison(suff)
223 | if err != nil {
224 | return err
225 | }
226 |
227 | fuzzer.Comparison[tyname.ToString()] = fundecl
228 | }
229 |
230 | // "@generator:"
231 | suff, ok = matchPrefix(line, "@generator:")
232 | if ok {
233 | tyname, genfunc, stateful, err := parseGenerator(suff)
234 | if err != nil {
235 | return err
236 | }
237 |
238 | fuzzer.Generator[tyname.ToString()] = Generator{IsStateful: stateful, Name: genfunc}
239 | }
240 |
241 | // "@generator state:"
242 | suff, ok = matchPrefix(line, "@generator state:")
243 | if ok {
244 | state, err := parseGeneratorState(suff)
245 | if err != nil {
246 | return err
247 | }
248 |
249 | fuzzer.GeneratorState = state
250 | }
251 |
252 | return nil
253 | }
254 |
255 | // Parse a "@fuzz interface:"
256 | //
257 | // SYNTAX: Name
258 | func parseFuzzInterface(line string) (string, error) {
259 | var (
260 | name string
261 | err error
262 | rest string
263 | )
264 |
265 | name, rest = parseName(line)
266 |
267 | if name == "" {
268 | err = fmt.Errorf("expected a name in '%s'", line)
269 | } else if rest != "" {
270 | err = fmt.Errorf("unexpected left over input in '%s' (got '%s')", line, rest)
271 | }
272 |
273 | return name, err
274 | }
275 |
276 | // Parse a "@known correct:"
277 | //
278 | // SYNTAX: [&] FunctionName [ArgType1 ... ArgTypeN]
279 | func parseKnownCorrect(line string) (Function, bool, error) {
280 | var function Function
281 |
282 | if len(line) == 0 {
283 | return function, false, errors.New("@known correct has empty argument")
284 | }
285 |
286 | // [&]
287 | rest, returnsValue := matchPrefix(line, "&")
288 |
289 | // FunctionName
290 | if len(line) == 0 {
291 | return function, false, errors.New("@known correct must have a function name")
292 | }
293 |
294 | function.Name, rest = parseFunctionName(rest)
295 |
296 | // [ArgType1 ... ArgTypeN]
297 | var args []Type
298 | for rest != "" {
299 | var argty Type
300 | var err error
301 | argty, rest, err = parseType(rest)
302 |
303 | if err != nil {
304 | return function, false, err
305 | }
306 |
307 | args = append(args, argty)
308 | }
309 | function.Parameters = args
310 |
311 | return function, returnsValue, nil
312 | }
313 |
314 | // Parse a "@comparison:"
315 | //
316 | // SYNTAX: (Type:FunctionName | FunctionName Type)
317 | func parseComparison(line string) (Type, EitherFunctionOrMethod, error) {
318 | funcOrMeth, rest, err := parseFunctionOrMethod(line)
319 |
320 | if err != nil {
321 | return nil, funcOrMeth, err
322 | }
323 | if rest != "" {
324 | return nil, funcOrMeth, fmt.Errorf("unexpected left over input in '%s' (got '%s')", line, rest)
325 | }
326 |
327 | return funcOrMeth.Type, funcOrMeth, err
328 | }
329 |
330 | // Parse a "@generator:"
331 | //
332 | // SYNTAX: [!] FunctionName Type
333 | func parseGenerator(line string) (Type, string, bool, error) {
334 | // [!]
335 | rest, stateful := matchPrefix(line, "!")
336 |
337 | // FunctionName
338 | var name string
339 | name, rest = parseFunctionName(rest)
340 |
341 | if name == "" {
342 | return nil, name, stateful, fmt.Errorf("expected a name in '%s'", line)
343 | }
344 |
345 | var err error
346 | var ty Type
347 | ty, rest, err = parseType(rest)
348 |
349 | if rest != "" {
350 | err = fmt.Errorf("unexpected left over input in '%s' (got '%s')", line, rest)
351 | }
352 |
353 | return ty, name, stateful, err
354 | }
355 |
356 | // Parse a "@generator state:"
357 | //
358 | // This does absolutely NO checking whatsoever beyond presence
359 | // checking!
360 | //
361 | // SYNTAX: Expression
362 | func parseGeneratorState(line string) (string, error) {
363 | if line == "" {
364 | return "", fmt.Errorf("expected an initial state")
365 | }
366 |
367 | return line, nil
368 | }
369 |
370 | // Parse an "@invariant:"
371 | //
372 | // This does absolutely NO checking whatsoever beyond presence
373 | // checking!
374 | //
375 | // SYNTAX: Expression
376 | func parseInvariant(line string) (string, error) {
377 | if line == "" {
378 | return "", fmt.Errorf("expected an expression")
379 | }
380 |
381 | return line, nil
382 | }
383 |
384 | // Parse a function or a method, returning the remainder of the
385 | // string, which has leading spaces stripped.
386 | //
387 | // SYNTAX: (Type:FunctionName | FunctionName Type)
388 | func parseFunctionOrMethod(line string) (EitherFunctionOrMethod, string, error) {
389 | var (
390 | funcOrMeth EitherFunctionOrMethod
391 | rest string
392 | err error
393 | )
394 |
395 | // This is a bit tricky, as there is overlap between names and
396 | // types. Try parsing as both a name and a type: if the type
397 | // succeeds, assume it's a method and go with that; if not and the
398 | // name succeeds assume it's a function; and if neither succeed
399 | // give an error.
400 |
401 | tyType, tyRest, tyErr := parseType(line)
402 | nName, nRest := parseFunctionName(line)
403 |
404 | if tyErr == nil && tyRest[0] == ':' {
405 | // It's a method.
406 | funcOrMeth.Type = tyType
407 | funcOrMeth.Name, rest = parseFunctionName(tyRest[1:])
408 | } else if nName != "" {
409 | // It's a function
410 | funcOrMeth.Name = nName
411 | funcOrMeth.Type, rest, err = parseType(nRest)
412 | funcOrMeth.IsFunction = true
413 | } else {
414 | err = fmt.Errorf("'%s' does not appear to be a method or function", line)
415 | }
416 |
417 | return funcOrMeth, rest, err
418 | }
419 |
420 | // Parse a function name, returning the remainder of the string, which
421 | // has leading spaces stripped.
422 | //
423 | // SYNTAX: [ModuleName.].FunctionName
424 | func parseFunctionName(line string) (string, string) {
425 | var (
426 | name string
427 | rest string
428 | )
429 |
430 | // Parse a name and see if the next character is a '.'.
431 | pref, suff := parseName(line)
432 | suff = strings.TrimLeftFunc(suff, unicode.IsSpace)
433 |
434 | if len(suff) > 0 && suff[0] == '.' {
435 | modname := pref
436 | funcname, suff2 := parseName(suff[1:])
437 | name = modname + "." + funcname
438 | rest = strings.TrimLeftFunc(suff2, unicode.IsSpace)
439 | } else {
440 | name = pref
441 | rest = suff
442 | }
443 |
444 | return name, rest
445 |
446 | }
447 |
448 | // Parse a type. This is very stupid and doesn't make much effort to
449 | // be absolutely correct.
450 | //
451 | // SYNTAX: []Type | chan Type | map[Type]Type | *Type | (Type) | Name.Type | Name
452 | func parseType(s string) (Type, string, error) {
453 | // Array type
454 | suff, ok := matchPrefix(s, "[]")
455 | if ok {
456 | tycon := func(t Type) Type {
457 | ty := ArrayType{ElementType: t}
458 | return &ty
459 | }
460 | return parseUnaryType(tycon, suff, s)
461 | }
462 |
463 | // Chan type
464 | suff, ok = matchPrefix(s, "chan")
465 | if ok {
466 | tycon := func(t Type) Type {
467 | ty := ChanType{ElementType: t}
468 | return &ty
469 | }
470 | return parseUnaryType(tycon, suff, s)
471 | }
472 |
473 | // Map type
474 | suff, ok = matchPrefix(s, "map[")
475 | if ok {
476 | keyTy, keyRest, keyErr := parseType(suff)
477 | suff, ok = matchPrefix(keyRest, "]")
478 | if ok && keyErr == nil {
479 | tycon := func(t Type) Type {
480 | ty := MapType{KeyType: keyTy, ValueType: t}
481 | return &ty
482 | }
483 | return parseUnaryType(tycon, keyRest[1:], s)
484 | }
485 | return nil, s, fmt.Errorf("Mismatched brackets in '%s'", s)
486 | }
487 |
488 | // Pointer type
489 | suff, ok = matchPrefix(s, "*")
490 | if ok {
491 | tycon := func(t Type) Type {
492 | ty := PointerType{TargetType: t}
493 | return &ty
494 | }
495 | return parseUnaryType(tycon, suff, s)
496 | }
497 |
498 | // Type in (posibly 0) parentheses
499 | noParens, parenOk := matchDelims(s, "(", ")")
500 | if parenOk {
501 | // Basic type OR qualified type
502 | if noParens == s {
503 | pref, suff := parseName(s)
504 | suff = strings.TrimLeftFunc(suff, unicode.IsSpace)
505 |
506 | if len(suff) > 0 && suff[0] == '.' {
507 | pkg := pref
508 | tyname, suff2 := parseName(suff[1:])
509 | ty := BasicType(tyname)
510 | rest := strings.TrimLeftFunc(suff2, unicode.IsSpace)
511 | qty := QualifiedType{Package: pkg, Type: &ty}
512 | return &qty, rest, nil
513 | }
514 | basicTy := BasicType(pref)
515 | rest := suff
516 | return &basicTy, rest, nil
517 | }
518 |
519 | return parseType(noParens)
520 | }
521 |
522 | return nil, s, fmt.Errorf("mismatched parentheses in '%s'", s)
523 | }
524 |
525 | // Helper function for parsing a unary type operator: [], chan, or *.
526 | //
527 | // SYNTAX: Type
528 | func parseUnaryType(tycon func(Type) Type, s, orig string) (Type, string, error) {
529 | var (
530 | innerTy Type
531 | rest string
532 | err error
533 | )
534 |
535 | noSpaces := strings.TrimLeft(s, " ")
536 | noParens, parenOk := matchDelims(noSpaces, "(", ")")
537 |
538 | if parenOk {
539 | innerTy, rest, err = parseType(noParens)
540 | } else {
541 | err = fmt.Errorf("mismatched parentheses in '%s'", orig)
542 | }
543 |
544 | return tycon(innerTy), rest, err
545 | }
546 |
547 | // Parse a name.
548 | //
549 | // SYNTAX: [a-zA-Z0-9_-]
550 | func parseName(s string) (string, string) {
551 | name, suff := takeWhileIn(s, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_-")
552 | rest := strings.TrimLeftFunc(suff, unicode.IsSpace)
553 | return name, rest
554 | }
555 |
--------------------------------------------------------------------------------