├── .dockerignore ├── .github └── CODEOWNERS ├── .gitignore ├── .spelling ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Makefile ├── README.md ├── codeship-services.yml ├── codeship-steps.yml ├── concurrency └── README.md ├── dependencies └── README.md ├── errors └── README.md ├── makefiles ├── README.md └── examples │ └── Makefile ├── package.json ├── reflection └── README.md ├── testing ├── README.md └── examples │ └── table-driven │ ├── fib.go │ ├── fib_test.go │ └── readme.md └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | ./node_modules/ 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | * @georgemac @markphelps @brettbuddin 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.spelling: -------------------------------------------------------------------------------- 1 | # markdown-spellcheck spelling configuration file 2 | # Format - lines beginning # are comments 3 | # global dictionary is at the start, file overrides afterwards 4 | # one word per line, to define a file override use ' - filename' 5 | # where filename is relative to this configuration file 6 | Codeship 7 | golang 8 | goroutines 9 | struct 10 | fibonacci 11 | noops 12 | TODO 13 | TLDR 14 | onboarding 15 | mdspell 16 | subtests 17 | dep 18 | 80 19 | 100 20 | Hashimoto's 21 | subtests 22 | impl 23 | i.e. 24 | makefiles 25 | makefile 26 | codebases 27 | sexualized 28 | https 29 | unmarshal 30 | unbuffered 31 | goroutine 32 | WaitGroup 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting one of the project maintainers listed below. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Project Maintainers 69 | 70 | * Codeship Engineering Team 71 | 72 | ## Attribution 73 | 74 | This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.4, 75 | available at [https://contributor-covenant.org/version/1/4](https://contributor-covenant.org/version/1/4/). 76 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ADD . /repo 4 | 5 | WORKDIR /repo 6 | 7 | RUN npm install 8 | 9 | CMD ["npm", "test"] 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: spellcheck 2 | spellcheck: 3 | yarn test 4 | 5 | .PHONY: fix-spelling 6 | fix-spelling: 7 | yarn run fix 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Codeship Golang Best Practices 2 | ============================= 3 | 4 | ## Motivation 5 | 6 | To achieve consistency and simplicity throughout our Go codebases. This is to aid in readability, maintainability and efficiency when onboarding and during code review. 7 | 8 | This document is built upon and inspired by a number of articles and talks given over the past few years by the Go community. These practices are not unique, [everything is borrowed](https://www.youtube.com/watch?v=j8BHL5SWX0Q). 9 | 10 | ## Contents 11 | 12 | - [testing](./testing) 13 | - [errors](./errors) 14 | - [concurrency](./concurrency) 15 | - [makefiles](./makefiles) 16 | - [dependency management](./dependencies) 17 | - [reflection](./reflection) 18 | 19 | ## Resources 20 | 21 | ## Extra Goodies 22 | 23 | We run markdown-spellcheck in CI on all contributions. Failure to amend such errors will result in changes being rejected. 24 | 25 | `make spellcheck` to get quick validation 26 | 27 | `make fix-spelling` to use `mdspell` interactive correction functionality and to maintain the `.spelling` file. 28 | 29 | see output and see [mdspell](https://github.com/lukeapage/node-markdown-spellcheck) for details on how to maintain the `.spelling` file. 30 | 31 | ## Contributing 32 | 33 | Everyone interacting in the project and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md). 34 | -------------------------------------------------------------------------------- /codeship-services.yml: -------------------------------------------------------------------------------- 1 | spellcheck: 2 | build: 3 | image: codeship/spellcheck 4 | dockerfile_path: Dockerfile 5 | -------------------------------------------------------------------------------- /codeship-steps.yml: -------------------------------------------------------------------------------- 1 | - service: spellcheck 2 | command: make spellcheck 3 | -------------------------------------------------------------------------------- /concurrency/README.md: -------------------------------------------------------------------------------- 1 | Concurrency in Go 2 | ================= 3 | 4 | [I came for the easy concurrency I stayed for the easy composition](https://www.youtube.com/watch?v=woCg2zaIVzQ) 5 | 6 | ## TLDR 7 | 8 | 1. It is really hard to do it *correctly*. Try your best to not use it at all. 9 | 1. It is really hard to test. Try your best to not use it at all. 10 | 1. Test concurrent interaction with the things you write, run those tests with the race detector. 11 | 12 | ## Concurrency in Go is simple 13 | 14 | Doing it correctly is really really hard. It is easy to introduce race conditions, lose control of resources or bring things to a complete standstill. 15 | As often as possible we will try to avoid using it. Often our problems can be solved in the following order: 16 | 17 | 1. Start by defining your code synchronously. 18 | 2. Refactor the type to be defensive against concurrent access. 19 | 3. Then let consumers of the type use it concurrently. 20 | 21 | ## Ownership and Responsibility 22 | 23 | ### **Don't** loose contact with your goroutines 24 | 25 | How can this be avoided? `make(chan struct{})`, `sync.WaitGroup`, `context.Context` and `select` are your friends. 26 | 27 | It is likely that your type should be: 28 | 29 | 1. Able to interrupt the goroutines it spawned when necessary. 30 | 2. Concerned with waiting until all the goroutines it produced are finished. 31 | 32 | #### Interruption 33 | 34 | This can be achieved by: 35 | 36 | 1. Sharing an unbuffered empty struct channel (`make(chan struct{})`) which is closed by the goroutine producer to signal a shutdown. 37 | 2. A cancellable [`context.Context`](https://golang.org/pkg/context/#WithCancel). 38 | 3. Ensuring your goroutines use `select` to check-in on their signal from time to time, without blocking on them indefinitely. 39 | 40 | #### Waiting for goroutines to finish 41 | 42 | The simplest way to achieve this is with a `sync.WaitGroup`. 43 | **Before** you run that `go routine()`, make sure you `wg.Add(1)`. 44 | **After** you run that `go routine()`, but before it `return`s, make sure you `wg.Done()`. This is where `defer` is your friend. 45 | 46 | ### **Don't** use wait groups to count more than one type of goroutine 47 | 48 | Goroutine type in this scenario relates to the type of the function being called as a goroutine. 49 | This function could be a member of another type, be a named function in the package or it could be anonymous. 50 | The important take-away is that you shouldn't share the WaitGroup among different functions called as goroutines. 51 | Keep it simple and add another WaitGroup if you find yourself calling `go` before a different function and name the WaitGroup's appropriately. 52 | 53 | ```go 54 | type Parent struct { 55 | wgFoo sync.WaitGroup 56 | wgBar sync.WaitGroup 57 | } 58 | 59 | func (p *Parent) foo() { 60 | defer p.wgFoo.Done() 61 | } 62 | 63 | func (p *Parent) bar() { 64 | defer p.wgBar.Done() 65 | } 66 | 67 | func (p *Parent) Go() { 68 | p.wgFoo.Add(1) 69 | go p.foo() 70 | 71 | p.wgBar.Add(1) 72 | go.bar() 73 | } 74 | ``` 75 | 76 | Though sharing a WaitGroup may be a correct solution, it adds to the cognitive complexity of a problem when the next engineer comes to grok it. 77 | 78 | ### **Don't** let a channel consumer say when it is done 79 | 80 | > A send on a closed channel will cause a panic. 81 | 82 | First and foremost this mandates that the code is modeled as channel __consumers__ and __producers__. 83 | This is a good practice in and of itself. It is a clear separations of concerns. 84 | 85 | Go gives you the ability to, at compile time, define the direction of a channel `recvOnly <-chan Thing := make(chan Thing)`. 86 | This is rarely useful when defining a variable, however, it is super useful when defining the receive arguments of a function. 87 | For example: 88 | 89 | ```go 90 | func consume(things <-chan Thing) { 91 | // will do work until close 92 | for thing := range things { 93 | // do work 94 | } 95 | } 96 | ``` 97 | 98 | This enforces (at compile time!) that the consumer goroutine **cannot** send on that channel. 99 | This includes the ability to **close** that channel. 100 | 101 | The aids in enforcing another tenant of safe channel management. Only close a channel, once all producers have stopped producing. Remember a send on a closed channel will cause a panic. 102 | It is important that you maintain responsibility over producers. 103 | 104 | **The piece of code which closes a channel must first guarantee that nothing else will produce on it.** 105 | 106 | If all sends on that channel have happened synchronously before the call to close, then you will be safe as long as you don't accidentally try and send again. If production on that channel is relinquished to other goroutines, then you need to be able to synchronize with the exit of these producing routines. 107 | 108 | If we did the work to ensure we are counting our routines and waiting for them to exit, then we can be sure that a close won't cause a panic elsewhere. 109 | 110 | ```go 111 | func doConcurrently() { 112 | var ( 113 | things = make(chan Thing) 114 | finished = make(chan struct{}) 115 | wg sync.WaitGroup 116 | ) 117 | 118 | go func() { 119 | // will consume until close 120 | consume(things) 121 | // signal consumption has finished 122 | close(finished) 123 | }() 124 | 125 | for i := 0; i < noOfThingsWeWantToDo; i++ { 126 | wg.Add(1) 127 | go func() { 128 | defer wg.Done() 129 | 130 | things <- Thing{} 131 | }() 132 | } 133 | 134 | // wait until all producers have stopped 135 | wg.Wait() 136 | 137 | // then you can close 138 | close(things) 139 | 140 | // wait until finished consuming 141 | <-finished 142 | } 143 | ``` 144 | 145 | ## Summary 146 | 147 | 1. Ensure consumers can only consume. `recvOnly <-chan Thing` are your friends. 148 | 2. Track completion of goroutines. `sync.WaitGroup` is your friend. 149 | 3. Close only when producing routines can be verified as no longer able to send on the channel being closed. 150 | -------------------------------------------------------------------------------- /dependencies/README.md: -------------------------------------------------------------------------------- 1 | Dependency Management 2 | ===================== 3 | 4 | 1. We use [dep](https://github.com/golang/dep). 5 | 1. Any issues we have with `dep` we vow to try and resolve through participation in the `dep` project. 6 | 1. We use `dep ensure` + `dep prune` until the two become one. 7 | -------------------------------------------------------------------------------- /errors/README.md: -------------------------------------------------------------------------------- 1 | Errors - Definition and Handling 2 | ================================ 3 | 4 | 1. We strive to create sensibly named root error types which adheres to `golint` standards for errors 5 | 6 | For example: 7 | 8 | ```go 9 | var ( 10 | // ErrSomethingBadHappened is returned when something bad happened 11 | ErrSomethingBadHappened = errors.New("something bad happened") 12 | ) 13 | ``` 14 | 15 | 2. We use enrich errors with contextual information where possible. This is done via the `errors.Wrap` family of operations. The wrapping message should contain information with regards to the surrounding function, rather than focusing on the function being called. With an expectation that the error being returned contains all the necessary context. 16 | 17 | For example: 18 | 19 | ```go 20 | func Process(thing Thing) error { 21 | if err := util.DoSomething(thing.Property); err != nil { 22 | return errors.Wrapf(err, "processing %q", thing) 23 | } 24 | } 25 | ``` 26 | 27 | The expectation is that the error returned from `util.DoSomething(...)` contains any relevant context with regards to the `DoSomething` function. Where at the point of collecting the error in `Process(...)` we have the opportunity to enrich the error with context regarding the fact we are processing a particular `Thing` when we encountered this error. 28 | 29 | 3. All error messages are lower-case 30 | 31 | ```go 32 | return errors.New("always in lower-case") 33 | ``` 34 | 35 | ## Third Party Tools 36 | 37 | - [pkg/errors](https://github.com/pkg/errors) - Dave Cheney's error handling primitives package 38 | -------------------------------------------------------------------------------- /makefiles/README.md: -------------------------------------------------------------------------------- 1 | Makefiles 2 | ========= 3 | 4 | Use a common [Makefile](https://en.wikipedia.org/wiki/Makefile) to reduce repetitive tasks. 5 | 6 | This can be iterated on/added to as needed on a per project basis, but having a generic one as a good foundation will make it easier for developers to move between projects. 7 | 8 | An example can be found at: [examples/Makefile](examples/Makefile) 9 | 10 | ``` 11 | ❯ make help 12 | setup Install all the build and lint dependencies 13 | dep Run dep ensure and prune 14 | test Run all the tests 15 | cover Run all the tests and opens the coverage report 16 | fmt Run goimports on all go files 17 | lint Run all the linters 18 | ci Run all the tests and code checks 19 | build Build a version 20 | clean Remove temporary files 21 | ``` 22 | -------------------------------------------------------------------------------- /makefiles/examples/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: setup 2 | setup: ## Install all the build and lint dependencies 3 | go get -u github.com/alecthomas/gometalinter 4 | go get -u golang.org/x/tools/cmd/cover 5 | go get -u github.com/golang/dep/cmd/dep 6 | gometalinter --install --update 7 | @$(MAKE) dep 8 | 9 | .PHONY: dep 10 | dep: ## Run dep ensure and prune 11 | dep ensure 12 | dep prune 13 | 14 | .PHONY: test 15 | test: ## Run all the tests 16 | echo 'mode: atomic' > coverage.txt && go test -covermode=atomic -coverprofile=coverage.txt -v -race -timeout=30s ./... 17 | 18 | .PHONY: cover 19 | cover: test ## Run all the tests and opens the coverage report 20 | go tool cover -html=coverage.txt 21 | 22 | .PHONY: fmt 23 | fmt: ## Run goimports on all go files 24 | find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do goimports -w "$$file"; done 25 | 26 | .PHONY: lint 27 | lint: ## Run all the linters 28 | gometalinter --vendor --disable-all \ 29 | --enable=deadcode \ 30 | --enable=ineffassign \ 31 | --enable=gosimple \ 32 | --enable=staticcheck \ 33 | --enable=gofmt \ 34 | --enable=goimports \ 35 | --enable=misspell \ 36 | --enable=errcheck \ 37 | --enable=vet \ 38 | --enable=vetshadow \ 39 | --deadline=10m \ 40 | ./... 41 | 42 | .PHONY: ci 43 | ci: lint test ## Run all the tests and code checks 44 | 45 | .PHONY: build 46 | build: ## Build a version 47 | go build -v ./... 48 | 49 | .PHONY: clean 50 | clean: ## Remove temporary files 51 | go clean 52 | 53 | # Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html 54 | .PHONY: help 55 | help: 56 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 57 | 58 | .DEFAULT_GOAL := build 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "go-best-practices", 3 | "version": "0.1.0", 4 | "description": "Codeship Go best practices documents the standards we hold ourselves to, when delivering Golang projects.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mdspell -r -n -a --en-us '{,!(node_modules)/**/}*.md'", 8 | "fix": "mdspell -n -a --en-us '{,!(node_modules)/**/}*.md'" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/codeship/go-best-practices.git" 13 | }, 14 | "keywords": [ 15 | "go-best-practices", 16 | "golang", 17 | "codeship" 18 | ], 19 | "author": "George MacRorie", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/codeship/go-best-practices/issues" 23 | }, 24 | "homepage": "https://github.com/codeship/go-best-practices#readme", 25 | "dependencies": { 26 | "markdown-spellcheck": "~1.3.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /reflection/README.md: -------------------------------------------------------------------------------- 1 | Reflection 2 | ========== 3 | 4 | 1. Mostly just NO. 5 | 2. Reflection is useful for things which unmarshal (like `encoding/json`) and parsing of struct tags, but we rarely ever implement this stuff. 6 | 3. A third party tool can use reflection, but if we see them using reflection, we have to ask ourselves why are they using it? Does this give us more or less confidence in that tool. 7 | -------------------------------------------------------------------------------- /testing/README.md: -------------------------------------------------------------------------------- 1 | Testing in Go 2 | ============= 3 | 4 | ## The Minimum Standard 5 | 6 | *80%* Coverage (preferably 100% happy path) 7 | 8 | ## TLDR 9 | 10 | 1. `testing` package is your friend. 11 | 1. `https://github.com/stretchr/testify` assertions and requirements are more than welcome. 12 | 1. table driven tests are often worthwhile. 13 | 1. tests will dictate how the production code is structured. 14 | 1. the minimum standard of coverage will be upheld. 15 | 1. we have decided against the use of mocking libraries 16 | 17 | ## Unit Testing 18 | 19 | Most practical ideas have been stolen from Mitchell Hashimoto's very comprehensive talk on advanced testing in Go [video][1]. 20 | 21 | ### Tests must be written before code reaches master 22 | 23 | Because tests can't be written later down the line. The way in which tests are written dictates the way in which the go we are testing is written. 24 | 25 | Any code that doesn't have suitable testing is subject to being thrown away and written from scratch with tests. In fact it is urged that is done as soon as possible. 26 | 27 | ### Use the standard libraries 28 | 29 | The `testing` package is your friend. It comes equipped with nearly everything you need to raise failures, create sub tests and perform suite setup and tear down. 30 | 31 | ### We like testify 32 | 33 | Simple comparisons are good enough to test with. However, it can get tedious and inconsistent to write our own failure messages. `assert` and `require` reduce the noise in a test and provide nicely formatted default failure messages. Plus it works very well with the standard libraries. 34 | 35 | ### Table driven tests 36 | 37 | 1. Use subtests to give each case of the table its own context 38 | 1. Use name field in your table case to be more descriptive 39 | 1. Use comments in the struct definition to further elaborate a fields intent 40 | 41 | #### examples 42 | 43 | 1. [fibonacci](./examples/table-driven) 44 | 45 | ## Mocking 46 | 47 | Currently our stance is simple. We don't use any third party libraries for mocking. Instead we favor hand-rolled mocks, dummies, noops, fakes and spies. 48 | 49 | Here's an example: 50 | 51 | Say that we have an interface which when supplied with a "container ID", it is intended to stop that container from running. The interface for this functionality might look like this: 52 | 53 | ```go 54 | type ContainerStopper interface { 55 | Stop(containerID string) error 56 | } 57 | ``` 58 | 59 | Instead of turning to a mocking library, all sorts of hand-rolled tricks can be employed to swap this behavior out. 60 | 61 | ```go 62 | type spyContainerStopper struct { 63 | id string 64 | err error 65 | } 66 | 67 | func (s spyContainerStopper) Stop(id string) error { 68 | s.id = id 69 | return n.err 70 | } 71 | ``` 72 | 73 | The first example above is the simple spy-like implementation which captures the input `id` on calls to `Stop(...)`. It also returns the error configured on the struct in the `err` field. 74 | 75 | ```go 76 | type containerStopperFunc func(string) error 77 | 78 | func (c containerStopperFunc) Stop(s string) error { return c(s) } 79 | ``` 80 | 81 | The second allows for simple anonymous functions to be used in a test case as an implementation. Note that this is often only useful for smaller interfaces, often single function (but your interfaces should be small anyway). The following demonstrates an anonymous function which achieves the same ends as the struct implementation. 82 | 83 | ```go 84 | func TestSomething(t *testing.T) { 85 | var ( 86 | capturedID string 87 | spy ContainerStopper = containerStopperFunc(func(id string) error { 88 | capturedID = id 89 | return errors.New("something went wrong") 90 | }) 91 | ) 92 | 93 | //... 94 | } 95 | ``` 96 | 97 | If you are dealing with a big pesky interface, it is recommended that you use something like [impl](https://github.com/josharian/impl) to generate a skeleton and speed things up. 98 | 99 | ## Acceptance Testing 100 | 101 | > TODO: Here we will reach out to existing Go projects and fill out this section with what others are doing well. 102 | 103 | [1]: https://www.youtube.com/watch?v=yszygk1cpEc 104 | -------------------------------------------------------------------------------- /testing/examples/table-driven/fib.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | // Fib returns the nth number in the Fibonacci series. 4 | func Fib(n int) int { 5 | if n < 2 { 6 | return n 7 | } 8 | return Fib(n-1) + Fib(n-2) 9 | } 10 | -------------------------------------------------------------------------------- /testing/examples/table-driven/fib_test.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | type testCase struct { 9 | in int // input 10 | expected int // expected result 11 | } 12 | 13 | func newCase(in, expected int) testCase { return testCase{in, expected} } 14 | 15 | func (c testCase) Name() string { 16 | return fmt.Sprintf("Fib(%d) should equal %d", c.in, c.expected) 17 | } 18 | 19 | func (c testCase) Run(t *testing.T) { 20 | actual := Fib(c.in) 21 | if actual != c.expected { 22 | t.Errorf("Fib(%d): expected %d, actual %d", c.in, c.expected, actual) 23 | } 24 | } 25 | 26 | func TestFib(t *testing.T) { 27 | for _, testCase := range []testCase{ 28 | newCase(1, 1), 29 | newCase(2, 1), 30 | newCase(3, 2), 31 | newCase(4, 3), 32 | newCase(5, 5), 33 | newCase(6, 8), 34 | newCase(7, 13), 35 | } { 36 | t.Run(testCase.Name(), testCase.Run) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /testing/examples/table-driven/readme.md: -------------------------------------------------------------------------------- 1 | Fibonacci with Table Driven Tests 2 | ================================= 3 | 4 | Poached from [here](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go). Because Dave Cheney knows what he is talking about. 5 | 6 | usage: `go test -v ./...` 7 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-escapes@^1.1.0: 6 | version "1.4.0" 7 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" 8 | 9 | ansi-regex@^2.0.0: 10 | version "2.1.1" 11 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 12 | 13 | ansi-styles@^2.2.1: 14 | version "2.2.1" 15 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 16 | 17 | ansi-styles@^3.1.0: 18 | version "3.2.0" 19 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" 20 | dependencies: 21 | color-convert "^1.9.0" 22 | 23 | argparse@^1.0.7: 24 | version "1.0.9" 25 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 26 | dependencies: 27 | sprintf-js "~1.0.2" 28 | 29 | array-union@^1.0.1: 30 | version "1.0.2" 31 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" 32 | dependencies: 33 | array-uniq "^1.0.1" 34 | 35 | array-uniq@^1.0.1: 36 | version "1.0.3" 37 | resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" 38 | 39 | async@^2.1.4: 40 | version "2.6.0" 41 | resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" 42 | dependencies: 43 | lodash "^4.14.0" 44 | 45 | balanced-match@^1.0.0: 46 | version "1.0.0" 47 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 48 | 49 | brace-expansion@^1.1.7: 50 | version "1.1.8" 51 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" 52 | dependencies: 53 | balanced-match "^1.0.0" 54 | concat-map "0.0.1" 55 | 56 | chalk@^1.0.0: 57 | version "1.1.3" 58 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 59 | dependencies: 60 | ansi-styles "^2.2.1" 61 | escape-string-regexp "^1.0.2" 62 | has-ansi "^2.0.0" 63 | strip-ansi "^3.0.0" 64 | supports-color "^2.0.0" 65 | 66 | chalk@^2.0.1: 67 | version "2.3.0" 68 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" 69 | dependencies: 70 | ansi-styles "^3.1.0" 71 | escape-string-regexp "^1.0.5" 72 | supports-color "^4.0.0" 73 | 74 | cli-cursor@^1.0.1: 75 | version "1.0.2" 76 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" 77 | dependencies: 78 | restore-cursor "^1.0.1" 79 | 80 | cli-width@^2.0.0: 81 | version "2.2.0" 82 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" 83 | 84 | code-point-at@^1.0.0: 85 | version "1.1.0" 86 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 87 | 88 | color-convert@^1.9.0: 89 | version "1.9.1" 90 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" 91 | dependencies: 92 | color-name "^1.1.1" 93 | 94 | color-name@^1.1.1: 95 | version "1.1.3" 96 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 97 | 98 | commander@^2.8.1: 99 | version "2.14.0" 100 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.0.tgz#7b25325963e6aace20d3a9285b09379b0c2208b5" 101 | 102 | concat-map@0.0.1: 103 | version "0.0.1" 104 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 105 | 106 | concat-stream@^1.4.7: 107 | version "1.6.0" 108 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" 109 | dependencies: 110 | inherits "^2.0.3" 111 | readable-stream "^2.2.2" 112 | typedarray "^0.0.6" 113 | 114 | core-util-is@~1.0.0: 115 | version "1.0.2" 116 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 117 | 118 | create-thenable@~1.0.0: 119 | version "1.0.2" 120 | resolved "https://registry.yarnpkg.com/create-thenable/-/create-thenable-1.0.2.tgz#e2031720ccc9575d8cfa31f5c146e762a80c0534" 121 | dependencies: 122 | object.omit "~2.0.0" 123 | unique-concat "~0.2.2" 124 | 125 | escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: 126 | version "1.0.5" 127 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 128 | 129 | esprima@^4.0.0: 130 | version "4.0.0" 131 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" 132 | 133 | exit-hook@^1.0.0: 134 | version "1.1.1" 135 | resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" 136 | 137 | extend@^3.0.0: 138 | version "3.0.1" 139 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" 140 | 141 | external-editor@^1.1.0: 142 | version "1.1.1" 143 | resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b" 144 | dependencies: 145 | extend "^3.0.0" 146 | spawn-sync "^1.0.15" 147 | tmp "^0.0.29" 148 | 149 | figures@^1.3.5: 150 | version "1.7.0" 151 | resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" 152 | dependencies: 153 | escape-string-regexp "^1.0.5" 154 | object-assign "^4.1.0" 155 | 156 | for-in@^1.0.1: 157 | version "1.0.2" 158 | resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" 159 | 160 | for-own@^0.1.4: 161 | version "0.1.5" 162 | resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" 163 | dependencies: 164 | for-in "^1.0.1" 165 | 166 | fs.realpath@^1.0.0: 167 | version "1.0.0" 168 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 169 | 170 | glob@^7.0.3: 171 | version "7.1.2" 172 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 173 | dependencies: 174 | fs.realpath "^1.0.0" 175 | inflight "^1.0.4" 176 | inherits "2" 177 | minimatch "^3.0.4" 178 | once "^1.3.0" 179 | path-is-absolute "^1.0.0" 180 | 181 | globby@^6.1.0: 182 | version "6.1.0" 183 | resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" 184 | dependencies: 185 | array-union "^1.0.1" 186 | glob "^7.0.3" 187 | object-assign "^4.0.1" 188 | pify "^2.0.0" 189 | pinkie-promise "^2.0.0" 190 | 191 | has-ansi@^2.0.0: 192 | version "2.0.0" 193 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 194 | dependencies: 195 | ansi-regex "^2.0.0" 196 | 197 | has-flag@^2.0.0: 198 | version "2.0.0" 199 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" 200 | 201 | hunspell-spellchecker@^1.0.2: 202 | version "1.0.2" 203 | resolved "https://registry.yarnpkg.com/hunspell-spellchecker/-/hunspell-spellchecker-1.0.2.tgz#a10b0bd2fa00a65ab62a4c6b734ce496d318910e" 204 | 205 | inflight@^1.0.4: 206 | version "1.0.6" 207 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 208 | dependencies: 209 | once "^1.3.0" 210 | wrappy "1" 211 | 212 | inherits@2, inherits@^2.0.3, inherits@~2.0.3: 213 | version "2.0.3" 214 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 215 | 216 | inquirer@^1.0.0: 217 | version "1.2.3" 218 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918" 219 | dependencies: 220 | ansi-escapes "^1.1.0" 221 | chalk "^1.0.0" 222 | cli-cursor "^1.0.1" 223 | cli-width "^2.0.0" 224 | external-editor "^1.1.0" 225 | figures "^1.3.5" 226 | lodash "^4.3.0" 227 | mute-stream "0.0.6" 228 | pinkie-promise "^2.0.0" 229 | run-async "^2.2.0" 230 | rx "^4.1.0" 231 | string-width "^1.0.1" 232 | strip-ansi "^3.0.0" 233 | through "^2.3.6" 234 | 235 | is-extendable@^0.1.1: 236 | version "0.1.1" 237 | resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" 238 | 239 | is-fullwidth-code-point@^1.0.0: 240 | version "1.0.0" 241 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 242 | dependencies: 243 | number-is-nan "^1.0.0" 244 | 245 | is-promise@^2.1.0: 246 | version "2.1.0" 247 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 248 | 249 | isarray@~1.0.0: 250 | version "1.0.0" 251 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 252 | 253 | js-yaml@^3.10.0: 254 | version "3.10.0" 255 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" 256 | dependencies: 257 | argparse "^1.0.7" 258 | esprima "^4.0.0" 259 | 260 | lodash@^4.14.0, lodash@^4.3.0: 261 | version "4.17.5" 262 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" 263 | 264 | markdown-spellcheck@~1.3.1: 265 | version "1.3.1" 266 | resolved "https://registry.yarnpkg.com/markdown-spellcheck/-/markdown-spellcheck-1.3.1.tgz#e901b04631e759ad8903470db3261013c2712602" 267 | dependencies: 268 | async "^2.1.4" 269 | chalk "^2.0.1" 270 | commander "^2.8.1" 271 | globby "^6.1.0" 272 | hunspell-spellchecker "^1.0.2" 273 | inquirer "^1.0.0" 274 | js-yaml "^3.10.0" 275 | marked "^0.3.5" 276 | sinon-as-promised "^4.0.0" 277 | 278 | marked@^0.3.5: 279 | version "0.3.12" 280 | resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519" 281 | 282 | minimatch@^3.0.4: 283 | version "3.0.4" 284 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 285 | dependencies: 286 | brace-expansion "^1.1.7" 287 | 288 | mute-stream@0.0.6: 289 | version "0.0.6" 290 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" 291 | 292 | native-promise-only@~0.8.1: 293 | version "0.8.1" 294 | resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11" 295 | 296 | number-is-nan@^1.0.0: 297 | version "1.0.1" 298 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 299 | 300 | object-assign@^4.0.1, object-assign@^4.1.0: 301 | version "4.1.1" 302 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 303 | 304 | object.omit@~2.0.0: 305 | version "2.0.1" 306 | resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" 307 | dependencies: 308 | for-own "^0.1.4" 309 | is-extendable "^0.1.1" 310 | 311 | once@^1.3.0: 312 | version "1.4.0" 313 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 314 | dependencies: 315 | wrappy "1" 316 | 317 | onetime@^1.0.0: 318 | version "1.1.0" 319 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" 320 | 321 | os-shim@^0.1.2: 322 | version "0.1.3" 323 | resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" 324 | 325 | os-tmpdir@~1.0.1: 326 | version "1.0.2" 327 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 328 | 329 | path-is-absolute@^1.0.0: 330 | version "1.0.1" 331 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 332 | 333 | pify@^2.0.0: 334 | version "2.3.0" 335 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 336 | 337 | pinkie-promise@^2.0.0: 338 | version "2.0.1" 339 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 340 | dependencies: 341 | pinkie "^2.0.0" 342 | 343 | pinkie@^2.0.0: 344 | version "2.0.4" 345 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 346 | 347 | process-nextick-args@~1.0.6: 348 | version "1.0.7" 349 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 350 | 351 | readable-stream@^2.2.2: 352 | version "2.3.3" 353 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" 354 | dependencies: 355 | core-util-is "~1.0.0" 356 | inherits "~2.0.3" 357 | isarray "~1.0.0" 358 | process-nextick-args "~1.0.6" 359 | safe-buffer "~5.1.1" 360 | string_decoder "~1.0.3" 361 | util-deprecate "~1.0.1" 362 | 363 | restore-cursor@^1.0.1: 364 | version "1.0.1" 365 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" 366 | dependencies: 367 | exit-hook "^1.0.0" 368 | onetime "^1.0.0" 369 | 370 | run-async@^2.2.0: 371 | version "2.3.0" 372 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" 373 | dependencies: 374 | is-promise "^2.1.0" 375 | 376 | rx@^4.1.0: 377 | version "4.1.0" 378 | resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" 379 | 380 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 381 | version "5.1.1" 382 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 383 | 384 | sinon-as-promised@^4.0.0: 385 | version "4.0.3" 386 | resolved "https://registry.yarnpkg.com/sinon-as-promised/-/sinon-as-promised-4.0.3.tgz#c0545b1685fd813588a4ed697012487ed11d151b" 387 | dependencies: 388 | create-thenable "~1.0.0" 389 | native-promise-only "~0.8.1" 390 | 391 | spawn-sync@^1.0.15: 392 | version "1.0.15" 393 | resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" 394 | dependencies: 395 | concat-stream "^1.4.7" 396 | os-shim "^0.1.2" 397 | 398 | sprintf-js@~1.0.2: 399 | version "1.0.3" 400 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 401 | 402 | string-width@^1.0.1: 403 | version "1.0.2" 404 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 405 | dependencies: 406 | code-point-at "^1.0.0" 407 | is-fullwidth-code-point "^1.0.0" 408 | strip-ansi "^3.0.0" 409 | 410 | string_decoder@~1.0.3: 411 | version "1.0.3" 412 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" 413 | dependencies: 414 | safe-buffer "~5.1.0" 415 | 416 | strip-ansi@^3.0.0: 417 | version "3.0.1" 418 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 419 | dependencies: 420 | ansi-regex "^2.0.0" 421 | 422 | supports-color@^2.0.0: 423 | version "2.0.0" 424 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 425 | 426 | supports-color@^4.0.0: 427 | version "4.5.0" 428 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" 429 | dependencies: 430 | has-flag "^2.0.0" 431 | 432 | through@^2.3.6: 433 | version "2.3.8" 434 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 435 | 436 | tmp@^0.0.29: 437 | version "0.0.29" 438 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" 439 | dependencies: 440 | os-tmpdir "~1.0.1" 441 | 442 | typedarray@^0.0.6: 443 | version "0.0.6" 444 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 445 | 446 | unique-concat@~0.2.2: 447 | version "0.2.2" 448 | resolved "https://registry.yarnpkg.com/unique-concat/-/unique-concat-0.2.2.tgz#9210f9bdcaacc5e1e3929490d7c019df96f18712" 449 | 450 | util-deprecate@~1.0.1: 451 | version "1.0.2" 452 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 453 | 454 | wrappy@1: 455 | version "1.0.2" 456 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 457 | --------------------------------------------------------------------------------