Full Code of quii/learn-go-with-tests for AI

main 9b678a3d00ef cached
599 files
1.5 MB
427.9k tokens
2425 symbols
1 requests
Download .txt
Showing preview only (1,673K chars total). Download the full file or copy to clipboard to get everything.
Repository: quii/learn-go-with-tests
Branch: main
Commit: 9b678a3d00ef
Files: 599
Total size: 1.5 MB

Directory structure:
gitextract_n1nyvkdi/

├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── go.yml
├── .gitignore
├── .mdlrc
├── LICENSE.md
├── README.md
├── SUMMARY.md
├── anti-patterns.md
├── app-intro.md
├── arrays/
│   ├── v1/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v2/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v3/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v4/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v5/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v6/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v7/
│   │   ├── sum.go
│   │   └── sum_test.go
│   └── v8/
│       ├── assert.go
│       ├── bad_bank.go
│       ├── bad_bank_test.go
│       ├── collection_fun.go
│       ├── sum.go
│       └── sum_test.go
├── arrays-and-slices.md
├── blogrenderer/
│   ├── post.go
│   ├── renderer.go
│   ├── renderer_test.TestRender.it_converts_a_single_post_into_HTML.approved.txt
│   ├── renderer_test.TestRender.it_renders_an_index_of_posts.approved.txt
│   ├── renderer_test.go
│   └── templates/
│       ├── blog.gohtml
│       ├── bottom.gohtml
│       ├── index.gohtml
│       └── top.gohtml
├── book.json
├── build.books.sh
├── build.sh
├── command-line/
│   ├── v1/
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   └── tape_test.go
│   ├── v2/
│   │   ├── CLI.go
│   │   ├── CLI_test.go
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   └── tape_test.go
│   └── v3/
│       ├── CLI.go
│       ├── CLI_test.go
│       ├── cmd/
│       │   ├── cli/
│       │   │   └── main.go
│       │   └── webserver/
│       │       └── main.go
│       ├── file_system_store.go
│       ├── file_system_store_test.go
│       ├── league.go
│       ├── server.go
│       ├── server_integration_test.go
│       ├── server_test.go
│       ├── tape.go
│       ├── tape_test.go
│       └── testing.go
├── command-line.md
├── concurrency/
│   ├── v1/
│   │   ├── check_website.go
│   │   ├── check_websites.go
│   │   ├── check_websites_benchmark_test.go
│   │   └── check_websites_test.go
│   ├── v2/
│   │   ├── check_website.go
│   │   ├── check_websites.go
│   │   ├── check_websites_benchmark_test.go
│   │   └── check_websites_test.go
│   └── v3/
│       ├── check_website.go
│       ├── check_websites.go
│       ├── check_websites_benchmark_test.go
│       └── check_websites_test.go
├── concurrency.md
├── context/
│   ├── v1/
│   │   ├── context.go
│   │   └── context_test.go
│   ├── v2/
│   │   ├── context.go
│   │   ├── context_test.go
│   │   └── testdoubles.go
│   └── v3/
│       ├── context.go
│       ├── context_test.go
│       └── testdoubles.go
├── context-aware-reader.md
├── context.md
├── contributing.md
├── dependency-injection.md
├── di/
│   ├── v1/
│   │   ├── di.go
│   │   └── di_test.go
│   └── v2/
│       ├── di.go
│       └── di_test.go
├── epub-cover.pxm
├── error-types.md
├── for/
│   ├── v1/
│   │   ├── repeat.go
│   │   └── repeat_test.go
│   ├── v2/
│   │   ├── repeat.go
│   │   └── repeat_test.go
│   ├── v3/
│   │   ├── repeat.go
│   │   └── repeat_test.go
│   └── vx/
│       ├── repeat.go
│       └── repeat_test.go
├── gb-readme.md
├── generics/
│   ├── assert.go
│   ├── generics_test.go
│   └── stack.go
├── generics.md
├── go.mod
├── go.sum
├── hello-world/
│   ├── v1/
│   │   └── hello.go
│   ├── v2/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v3/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v4/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v5/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v6/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v7/
│   │   ├── hello.go
│   │   └── hello_test.go
│   └── v8/
│       ├── hello.go
│       └── hello_test.go
├── hello-world.md
├── html-templates.md
├── http-handlers-revisited.md
├── http-server/
│   ├── v1/
│   │   ├── main.go
│   │   ├── server.go
│   │   └── server_test.go
│   ├── v2/
│   │   ├── main.go
│   │   ├── server.go
│   │   └── server_test.go
│   ├── v3/
│   │   ├── main.go
│   │   ├── server.go
│   │   └── server_test.go
│   ├── v4/
│   │   ├── main.go
│   │   ├── server.go
│   │   └── server_test.go
│   └── v5/
│       ├── in_memory_player_store.go
│       ├── main.go
│       ├── server.go
│       ├── server_integration_test.go
│       └── server_test.go
├── http-server.md
├── install-go.md
├── integers/
│   ├── v1/
│   │   ├── adder.go
│   │   └── adder_test.go
│   └── v2/
│       ├── adder.go
│       └── adder_test.go
├── integers.md
├── intro-to-acceptance-tests.md
├── io/
│   ├── v1/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── in_memory_player_store.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v2/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── in_memory_player_store.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v3/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── in_memory_player_store.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v4/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── in_memory_player_store.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v5/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v6/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v7/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   └── tape_test.go
│   ├── v8/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   └── tape_test.go
│   └── v9/
│       ├── file_system_store.go
│       ├── file_system_store_test.go
│       ├── league.go
│       ├── main.go
│       ├── server.go
│       ├── server_integration_test.go
│       ├── server_test.go
│       ├── tape.go
│       └── tape_test.go
├── io.md
├── iteration.md
├── iterators/
│   └── iterators_test.go
├── json/
│   ├── v1/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v2/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v3/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v4/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v5/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   └── v6/
│       ├── in_memory_player_store.go
│       ├── main.go
│       ├── server.go
│       ├── server_integration_test.go
│       └── server_test.go
├── json.md
├── maps/
│   ├── v1/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v2/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v3/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v4/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v5/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v6/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   └── v7/
│       ├── dictionary.go
│       └── dictionary_test.go
├── maps.md
├── math/
│   ├── v1/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       └── clockface_test.go
│   ├── v10/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v11/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v12/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v2/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v3/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v4/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v5/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v6/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v7/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v7b/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v7c/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v8/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v9/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   └── vFinal/
│       └── clockface/
│           ├── clockface/
│           │   └── main.go
│           ├── clockface.go
│           ├── clockface_test.go
│           └── svg/
│               ├── svg.go
│               └── svg_test.go
├── math.md
├── meta.tmpl.tex
├── mocking/
│   ├── v1/
│   │   ├── countdown_test.go
│   │   └── main.go
│   ├── v2/
│   │   ├── countdown_test.go
│   │   └── main.go
│   ├── v3/
│   │   ├── countdown_test.go
│   │   └── main.go
│   ├── v4/
│   │   ├── countdown_test.go
│   │   └── main.go
│   ├── v5/
│   │   ├── countdown_test.go
│   │   └── main.go
│   └── v6/
│       ├── countdown_test.go
│       └── main.go
├── mocking.md
├── os-exec.md
├── pdf-cover.md
├── pdf-cover.tex
├── pointers/
│   ├── v1/
│   │   ├── wallet.go
│   │   └── wallet_test.go
│   ├── v2/
│   │   ├── wallet.go
│   │   └── wallet_test.go
│   ├── v3/
│   │   ├── wallet.go
│   │   └── wallet_test.go
│   └── v4/
│       ├── wallet.go
│       └── wallet_test.go
├── pointers-and-errors.md
├── q-and-a/
│   ├── context-aware-reader/
│   │   ├── context_aware_reader.go
│   │   └── context_aware_reader_test.go
│   ├── error-types/
│   │   ├── error-types_test.go
│   │   └── v2/
│   │       └── error-types_test.go
│   ├── http-handlers-revisited/
│   │   ├── basic_test.go
│   │   ├── still_basic.go
│   │   └── still_basic_test.go
│   └── os-exec/
│       ├── msg.xml
│       └── os-exec_test.go
├── reading-files/
│   ├── blogposts.go
│   ├── blogposts_test.go
│   └── post.go
├── reading-files.md
├── refactoring-checklist.md
├── reflection/
│   ├── v1/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v10/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v2/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v3/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v4/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v5/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v6/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v7/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v8/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   └── v9/
│       ├── reflection.go
│       └── reflection_test.go
├── reflection.md
├── revisiting-arrays-and-slices-with-generics.md
├── roman-numerals/
│   ├── v1/
│   │   └── numeral_test.go
│   ├── v10/
│   │   ├── numeral_test.go
│   │   └── roman_numerals.go
│   ├── v11/
│   │   ├── numeral_test.go
│   │   └── roman_numerals.go
│   ├── v2/
│   │   └── numeral_test.go
│   ├── v3/
│   │   └── numeral_test.go
│   ├── v4/
│   │   └── numeral_test.go
│   ├── v5/
│   │   └── numeral_test.go
│   ├── v6/
│   │   └── numeral_test.go
│   ├── v7/
│   │   └── numeral_test.go
│   ├── v8/
│   │   └── numeral_test.go
│   └── v9/
│       └── numeral_test.go
├── roman-numerals.md
├── scaling-acceptance-tests.md
├── select/
│   ├── v1/
│   │   ├── racer.go
│   │   └── racer_test.go
│   ├── v2/
│   │   ├── racer.go
│   │   └── racer_test.go
│   └── v3/
│       ├── racer.go
│       └── racer_test.go
├── select.md
├── structs/
│   ├── v1/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v2/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v3/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v4/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v5/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v6/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v7/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   └── v8/
│       ├── shapes.go
│       └── shapes_test.go
├── structs-methods-and-interfaces.md
├── sync/
│   ├── v1/
│   │   ├── sync.go
│   │   └── sync_test.go
│   └── v2/
│       ├── sync.go
│       └── sync_test.go
├── sync.md
├── template.md
├── time/
│   ├── v1/
│   │   ├── CLI.go
│   │   ├── CLI_test.go
│   │   ├── blind_alerter.go
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   ├── tape_test.go
│   │   └── testing.go
│   ├── v2/
│   │   ├── CLI.go
│   │   ├── CLI_test.go
│   │   ├── blind_alerter.go
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   ├── tape_test.go
│   │   ├── testing.go
│   │   ├── texas_holdem.go
│   │   └── texas_holdem_test.go
│   └── v3/
│       ├── BlindAlerter.go
│       ├── CLI.go
│       ├── CLI_test.go
│       ├── cmd/
│       │   ├── cli/
│       │   │   └── main.go
│       │   └── webserver/
│       │       └── main.go
│       ├── file_system_store.go
│       ├── file_system_store_test.go
│       ├── game.go
│       ├── league.go
│       ├── server.go
│       ├── server_integration_test.go
│       ├── server_test.go
│       ├── tape.go
│       ├── tape_test.go
│       ├── testing.go
│       ├── texas_holdem.go
│       └── texas_holdem_test.go
├── time.md
├── title.txt
├── todo/
│   └── todo1_test.go
├── websockets/
│   ├── v1/
│   │   ├── CLI.go
│   │   ├── CLI_test.go
│   │   ├── Gopkg.toml
│   │   ├── blind_alerter.go
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── game.go
│   │   ├── game.html
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   ├── tape_test.go
│   │   ├── testing.go
│   │   ├── texas_holdem.go
│   │   ├── texas_holdem_test.go
│   │   └── vendor/
│   │       └── github.com/
│   │           └── gorilla/
│   │               └── websocket/
│   │                   ├── .gitignore
│   │                   ├── .travis.yml
│   │                   ├── AUTHORS
│   │                   ├── LICENSE
│   │                   ├── README.md
│   │                   ├── client.go
│   │                   ├── client_clone.go
│   │                   ├── client_clone_legacy.go
│   │                   ├── compression.go
│   │                   ├── conn.go
│   │                   ├── conn_write.go
│   │                   ├── conn_write_legacy.go
│   │                   ├── doc.go
│   │                   ├── json.go
│   │                   ├── mask.go
│   │                   ├── mask_safe.go
│   │                   ├── prepared.go
│   │                   ├── proxy.go
│   │                   ├── server.go
│   │                   ├── trace.go
│   │                   ├── trace_17.go
│   │                   ├── util.go
│   │                   └── x_net_proxy.go
│   └── v2/
│       ├── CLI.go
│       ├── CLI_test.go
│       ├── Gopkg.toml
│       ├── blind_alerter.go
│       ├── cmd/
│       │   ├── cli/
│       │   │   └── main.go
│       │   └── webserver/
│       │       └── main.go
│       ├── file_system_store.go
│       ├── file_system_store_test.go
│       ├── game.go
│       ├── game.html
│       ├── league.go
│       ├── player_server_ws.go
│       ├── server.go
│       ├── server_integration_test.go
│       ├── server_test.go
│       ├── tape.go
│       ├── tape_test.go
│       ├── testing.go
│       ├── texas_holdem.go
│       ├── texas_holdem_test.go
│       └── vendor/
│           └── github.com/
│               └── gorilla/
│                   └── websocket/
│                       ├── .gitignore
│                       ├── .travis.yml
│                       ├── AUTHORS
│                       ├── LICENSE
│                       ├── README.md
│                       ├── client.go
│                       ├── client_clone.go
│                       ├── client_clone_legacy.go
│                       ├── compression.go
│                       ├── conn.go
│                       ├── conn_write.go
│                       ├── conn_write_legacy.go
│                       ├── doc.go
│                       ├── json.go
│                       ├── mask.go
│                       ├── mask_safe.go
│                       ├── prepared.go
│                       ├── proxy.go
│                       ├── server.go
│                       ├── trace.go
│                       ├── trace_17.go
│                       ├── util.go
│                       └── x_net_proxy.go
├── websockets.md
├── why.md
└── working-without-mocks.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# Top-most EditorConfig file
root = true

# Every file should according to these default configurations if not specified
[*]
# Use UNIX-style line endings
end_of_line = LF
# Use utf-8 file encoding
charset = utf-8
# 4 space indent
indent_style = space
indent_size = 4
# Ensure file ends with a newline when saving(prevent `no newline at EOF`)
insert_final_newline = true
# Remove any whitespace characters preceding newline characters
trim_trailing_whitespace = true

# For YAML
[*.{yml,yaml}]
indent_size = 2

# For Go files
[*.go]
# `gofmt` uses tabs for indentation
indent_style = tab


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [quii]


================================================
FILE: .github/workflows/go.yml
================================================
name: Go

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  release:
    types: [ published ]

jobs:

  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Set up Go
      uses: actions/setup-go@v5
      with:
        go-version: 1.24

    - name: Build
      run: ./build.sh

    - name: Build books
      run: ./build.books.sh

    - name: Get release
      id: get_release
      if: github.event_name == 'release' && github.event.action == 'published'
      uses: bruceadams/get-release@v1.3.2
      env:
        GITHUB_TOKEN: ${{ github.token }}

    - name: Attach generated epub upon release publish
      if: github.event_name == 'release' && github.event.action == 'published'
      with:
        upload_url: ${{ steps.get_release.outputs.upload_url }}
        asset_path: ./learn-go-with-tests.epub
        asset_name: learn-go-with-tests.epub
        asset_content_type: application/epub+zip
      uses: actions/upload-release-asset@v1.0.2
      env:
        GITHUB_TOKEN: ${{ github.token }}

    - name: Attach generated PDF upon release publish
      if: github.event_name == 'release' && github.event.action == 'published'
      with:
        upload_url: ${{ steps.get_release.outputs.upload_url }}
        asset_path: ./learn-go-with-tests.pdf
        asset_name: learn-go-with-tests.pdf
        asset_content_type: application/pdf
      uses: actions/upload-release-asset@v1.0.2
      env:
        GITHUB_TOKEN: ${{ github.token }}



================================================
FILE: .gitignore
================================================
.idea/
*.iml

# Book build output
_book/

# eBook build output
*.epub
*.mobi
*.pdf

# templated files
meta.tex

game.db.json
.DS_Store


================================================
FILE: .mdlrc
================================================
git_recurse true

rules "MD001", "MD002", "MD003", "MD004", "MD005", "MD006", "MD007", "MD008", "MD009", "MD010", "MD011", "MD012", "MD014", "MD015", "MD016", "MD017", "MD018", "MD019", "MD020", "MD021", "MD023", "MD025", "MD028", "MD030", "MD035", "MD037", "MD038", "MD040", "MD041", "MD042"

# exclude rules
exclude "MD013" # Line length
exclude "MD022" # Headers should be surrounded by blank lines
exclude "MD024" # Multiple headers with the same content
exclude "MD026" # Trailing punctuation in header
exclude "MD027" # Multiple spaces after blockquote symbol
exclude "MD029" # Ordered list item prefix
exclude "MD031" # Fenced code blocks should be surrounded by blank lines
exclude "MD032" # Lists should be surrounded by blank lines
exclude "MD033" # Inline HTML
exclude "MD034" # Bare URL used
exclude "MD036" # Emphasis used instead of a header
exclude "MD039" # Spaces inside link text


================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) [2025] [Christopher James]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Learn Go with Tests

<p align="center">
  <img src="red-green-blue-gophers-smaller.png" />
</p>

[Art by Denise](https://twitter.com/deniseyu21)

[![Go Report Card](https://goreportcard.com/badge/github.com/quii/learn-go-with-tests)](https://goreportcard.com/report/github.com/quii/learn-go-with-tests)

## Formats

- [Gitbook](https://quii.gitbook.io/learn-go-with-tests)
- [EPUB or PDF](https://github.com/quii/learn-go-with-tests/releases)

## Translations

- [中文](https://studygolang.gitbook.io/learn-go-with-tests)
- [Português](https://larien.gitbook.io/aprenda-go-com-testes/)
- [日本語](https://andmorefine.gitbook.io/learn-go-with-tests/)
- [Français](https://goosegeesejeez.gitbook.io/apprendre-go-par-les-tests)
- [한국어](https://miryang.gitbook.io/learn-go-with-tests/)
- [Türkçe](https://halilkocaoz.gitbook.io/go-programlama-dilini-ogren/)
- [فارسی](https://go-yaad-begir.gitbook.io/go-ba-test/)
- [Nederlands](https://bobkosse.gitbook.io/leer-go-met-tests/)

## Support me

I am proud to offer this resource for free, but if you wish to give some appreciation:

- [Tweet me @quii](https://twitter.com/quii)
- <a rel="me" href="https://mastodon.cloud/@quii">Mastodon</a>
- [Buy me a coffee :coffee:](https://www.buymeacoffee.com/quii)
- [Sponsor me on GitHub](https://github.com/sponsors/quii)

## Why

* Explore the Go language by writing tests
* **Get a grounding with TDD**. Go is a good language for learning TDD because it is a simple language to learn and testing is built-in
* Be confident that you'll be able to start writing robust, well-tested systems in Go
* [Watch a video, or read about why unit testing and TDD is important](why.md)

## Table of contents

### Go fundamentals

1. [Install Go](install-go.md) - Set up environment for productivity.
2. [Hello, world](hello-world.md) - Declaring variables, constants, if/else statements, switch, write your first go program and write your first test. Sub-test syntax and closures.
3. [Integers](integers.md) - Further Explore function declaration syntax and learn new ways to improve the documentation of your code.
4. [Iteration](iteration.md) - Learn about `for` and benchmarking.
5. [Arrays and slices](arrays-and-slices.md) - Learn about arrays, slices, `len`, varargs, `range` and test coverage.
6. [Structs, methods & interfaces](structs-methods-and-interfaces.md) - Learn about `struct`, methods, `interface` and table driven tests.
7. [Pointers & errors](pointers-and-errors.md) - Learn about pointers and errors.
8. [Maps](maps.md) - Learn about storing values in the map data structure.
9. [Dependency Injection](dependency-injection.md) - Learn about dependency injection, how it relates to using interfaces and a primer on io.
10. [Mocking](mocking.md) - Take some existing untested code and use DI with mocking to test it.
11. [Concurrency](concurrency.md) - Learn how to write concurrent code to make your software faster.
12. [Select](select.md) - Learn how to synchronise asynchronous processes elegantly.
13. [Reflection](reflection.md) - Learn about reflection
14. [Sync](sync.md) - Learn some functionality from the sync package including `WaitGroup` and `Mutex`
15. [Context](context.md) - Use the context package to manage and cancel long-running processes
16. [Intro to property based tests](roman-numerals.md) - Practice some TDD with the Roman Numerals kata and get a brief intro to property based tests
17. [Maths](math.md) - Use the `math` package to draw an SVG clock
18. [Reading files](reading-files.md) - Read files and process them
19. [Templating](html-templates.md) - Use Go's html/template package to render html from data, and also learn about approval testing
20. [Generics](generics.md) - Learn how to write functions that take generic arguments and make your own generic data-structure
21. [Revisiting arrays and slices with generics](revisiting-arrays-and-slices-with-generics.md) - Generics are very useful when working with collections. Learn how to write your own `Reduce` function and tidy up some common patterns.

### Build an application

Now that you have hopefully digested the _Go Fundamentals_ section you have a solid grounding of a majority of Go's language features and how to do TDD.

This next section will involve building an application.

Each chapter will iterate on the previous one, expanding the application's functionality as our product owner dictates.

New concepts will be introduced to help facilitate writing great code but most of the new material will be learning what can be accomplished from Go's standard library.

By the end of this, you should have a strong grasp as to how to iteratively write an application in Go, backed by tests.

* [HTTP server](http-server.md) - We will create an application which listens to HTTP requests and responds to them.
* [JSON, routing and embedding](json.md) - We will make our endpoints return JSON and explore how to do routing.
* [IO and sorting](io.md) - We will persist and read our data from disk and we'll cover sorting data.
* [Command line & project structure](command-line.md) - Support multiple applications from one code base and read input from command line.
* [Time](time.md) - using the `time` package to schedule activities.
* [WebSockets](websockets.md) - learn how to write and test a server that uses WebSockets.

### Testing fundamentals

Covering other subjects around testing.

* [Introduction to acceptance tests](intro-to-acceptance-tests.md) - Learn how to write acceptance tests for your code, with a real-world example for gracefully shutting down a HTTP server
* [Scaling acceptance tests](scaling-acceptance-tests.md) - Learn techniques to manage the complexity of writing acceptance tests for non-trivial systems.
* [Working without mocks, stubs and spies](working-without-mocks.md) - Learn about how to use fakes and contracts to create more realistic and maintainable tests.
* [Refactoring Checklist](refactoring-checklist.md) - Some discussion on what refactoring is, and some basic tips on how to do it.

### Questions and answers

I often run in to questions on the internets like

> How do I test my amazing function that does x, y and z

If you have such a question raise it as an issue on github and I'll try and find time to write a short chapter to tackle the issue. I feel like content like this is valuable as it is tackling people's _real_ questions around testing.

* [OS exec](os-exec.md) - An example of how we can reach out to the OS to execute commands to fetch data and keep our business logic testable/
* [Error types](error-types.md) - Example of creating your own error types to improve your tests and make your code easier to work with.
* [Context-aware Reader](context-aware-reader.md) - Learn how to TDD augmenting `io.Reader` with cancellation. Based on [Context-aware io.Reader for Go](https://pace.dev/blog/2020/02/03/context-aware-ioreader-for-golang-by-mat-ryer)
* [Revisiting HTTP Handlers](http-handlers-revisited.md) - Testing HTTP handlers seems to be the bane of many a developer's existence. This chapter explores the issues around designing handlers correctly.

### Meta / Discussion

* [Why unit tests and how to make them work for you](why.md) - Watch a video, or read about why unit testing and TDD is important
* [Anti-patterns](anti-patterns.md) - A short chapter on TDD and unit testing anti-patterns

## Contributing

* _This project is work in progress_ If you would like to contribute, please do get in touch.
* Read [contributing.md](https://github.com/quii/learn-go-with-tests/tree/842f4f24d1f1c20ba3bb23cbc376c7ca6f7ca79a/contributing.md) for guidelines
* Any ideas? Create an issue

## Background

I have some experience introducing Go to development teams and have tried different approaches as to how to grow a team from some people curious about Go into highly effective writers of Go systems.

### What didn't work

#### Read _the_ book

An approach we tried was to take [the blue book](https://www.amazon.co.uk/Programming-Language-Addison-Wesley-Professional-Computing/dp/0134190440) and every week discuss the next chapter along with the exercises.

I love this book but it requires a high level of commitment. The book is very detailed in explaining concepts, which is obviously great but it means that the progress is slow and steady - this is not for everyone.

I found that whilst a small number of people would read chapter X and do the exercises, many people didn't.

#### Solve some problems

Katas are fun but they are usually limited in their scope for learning a language; you're unlikely to use goroutines to solve a kata.

Another problem is when you have varying levels of enthusiasm. Some people just learn way more of the language than others and when demonstrating what they have done end up confusing people with features the others are not familiar with.

This ends up making the learning feel quite _unstructured_ and _ad hoc_.

### What did work

By far the most effective way was by slowly introducing the fundamentals of the language by reading through [go by example](https://gobyexample.com/), exploring them with examples and discussing them as a group. This was a more interactive approach than "read chapter x for homework".

Over time the team gained a solid foundation of the _grammar_ of the language so we could then start to build systems.

This to me seems analogous to practicing scales when trying to learn guitar.

It doesn't matter how artistic you think you are, you are unlikely to write good music without understanding the fundamentals and practicing the mechanics.

### What works for me

When _I_ learn a new programming language I usually start by messing around in a REPL but eventually, I need more structure.

What I like to do is explore concepts and then solidify the ideas with tests. Tests verify the code I write is correct and documents the feature I have learned.

Taking my experience of learning with a group and my own personal way I am going to try and create something that hopefully proves useful to other teams. Learning the fundamentals by writing small tests so that you can then take your existing software design skills and ship some great systems.

## Who this is for

* People who are interested in picking up Go.
* People who already know some Go, but want to explore testing with TDD.

## What you'll need

* A computer!
* [Installed Go](https://golang.org/)
* A text editor
* Some experience with programming. Understanding of concepts like `if`, variables, functions etc.
* Comfortable using the terminal

## Feedback

* Add issues/submit PRs [here](https://github.com/quii/learn-go-with-tests) or [tweet me @quii](https://twitter.com/quii)

[MIT license](LICENSE.md)

[Logo is by egonelbre](https://github.com/egonelbre) What a star!


================================================
FILE: SUMMARY.md
================================================
# Table of contents

* [Learn Go with Tests](gb-readme.md)

## Go fundamentals

* [Install Go](install-go.md)
* [Hello, World](hello-world.md)
* [Integers](integers.md)
* [Iteration](iteration.md)
* [Arrays and slices](arrays-and-slices.md)
* [Structs, methods & interfaces](structs-methods-and-interfaces.md)
* [Pointers & errors](pointers-and-errors.md)
* [Maps](maps.md)
* [Dependency Injection](dependency-injection.md)
* [Mocking](mocking.md)
* [Concurrency](concurrency.md)
* [Select](select.md)
* [Reflection](reflection.md)
* [Sync](sync.md)
* [Context](context.md)
* [Intro to property based tests](roman-numerals.md)
* [Maths](math.md)
* [Reading files](reading-files.md)
* [Templating](html-templates.md)
* [Generics](generics.md)
* [Revisiting arrays and slices with generics](revisiting-arrays-and-slices-with-generics.md)

## Testing fundamentals

* [Introduction to acceptance tests](intro-to-acceptance-tests.md)
* [Scaling acceptance tests](scaling-acceptance-tests.md)
* [Working without mocks](working-without-mocks.md)
* [Refactoring Checklist](refactoring-checklist.md)

## Build an application

* [Intro](app-intro.md)
* [HTTP server](http-server.md)
* [JSON, routing and embedding](json.md)
* [IO and sorting](io.md)
* [Command line & package structure](command-line.md)
* [Time](time.md)
* [WebSockets](websockets.md)

## Questions and answers

* [OS Exec](os-exec.md)
* [Error types](error-types.md)
* [Context-aware Reader](context-aware-reader.md)
* [Revisiting HTTP Handlers](http-handlers-revisited.md)

## Meta

* [Why unit tests and how to make them work for you](why.md)
* [Anti-patterns](anti-patterns.md)
* [Contributing](contributing.md)
* [Chapter Template](template.md)


================================================
FILE: anti-patterns.md
================================================
# TDD Anti-patterns

From time to time it's necessary to review your TDD techniques and remind yourself of behaviours to avoid.

The TDD process is conceptually simple to follow, but as you do it you'll find it challenging your design skills. **Don't mistake this for TDD being hard, it's design that's hard!**

This chapter lists a number of TDD and testing anti-patterns, and how to remedy them.

## Not doing TDD at all

Of course, it is possible to write great software without TDD but, a lot of problems I've seen with the design of code and the quality of tests would be very difficult to arrive at if a disciplined approach to TDD had been used.

One of the strengths of TDD is that it gives you a formal process to break down problems, understand what you're trying to achieve (red), get it done (green), then have a good think about how to make it right (blue/refactor).

Without this, the process is often ad-hoc and loose, which _can_ make engineering more difficult than it _could_ be.

## Misunderstanding the constraints of the refactoring step

I have been in a number of workshops, mobbing or pairing sessions where someone has made a test pass and is in the refactoring stage. After some thought, they think it would be good to abstract away some code into a new struct; a budding pedant yells:

> You're not allowed to do this! You should write a test for this first, we're doing TDD!

This seems to be a common misunderstanding. **You can do whatever you like to the code when the tests are green**, the only thing you're not allowed to do is **add or change behaviour**.

The point of these tests are to give you the _freedom to refactor_, find the right abstractions and make the code easier to change and understand.

## Having tests that won't fail (or, evergreen tests)

It's astonishing how often this comes up. You start debugging or changing some tests and realise: there are no scenarios where this test can fail. Or at least, it won't fail in the way the test is _supposed_ to be protecting against.

This is _next to impossible_ with TDD if you're following **the first step**,

> Write a test, see it fail

This is almost always done when developers write tests _after_ code is written, and/or chasing test coverage rather than creating a useful test suite.

## Useless assertions

Ever worked on a system, and you've broken a test, then you see this?

> `false was not equal to true`

I know that false is not equal to true. This is not a helpful message; it doesn't tell me what I've broken. This is a symptom of not following the TDD process and not reading the failure error message.

Going back to the drawing board,

> Write a test, see it fail (and don't be ashamed of the error message)

## Asserting on irrelevant detail

An example of this is making an assertion on a complex object, when in practice all you care about in the test is the value of one of the fields.

```go
// not this, now your test is tightly coupled to the whole object
if !cmp.Equal(complexObject, want) {
	t.Error("got %+v, want %+v", complexObject, want)
}

// be specific, and loosen the coupling
got := complexObject.fieldYouCareAboutForThisTest
if got != want {
	t.Error("got %q, want %q", got, want)
}
```

Additional assertions not only make your test more difficult to read by creating 'noise' in your documentation, but also needlessly couples the test with data it doesn't care about. This means if you happen to change the fields for your object, or the way they behave you may get unexpected compilation problems or failures with your tests.

This is an example of not following the red stage strictly enough.

- Letting an existing design influence how you write your test **rather than thinking of the desired behaviour**
- Not giving enough consideration to the failing test's error message

## Lots of assertions within a single scenario for unit tests

Many assertions can make tests difficult to read and challenging to debug when they fail.

They often creep in gradually, especially if test setup is complicated because you're reluctant to replicate the same horrible setup to assert on something else. Instead of this you should fix the problems in your design which are making it difficult to assert on new things.

A helpful rule of thumb is to aim to make one assertion per test. In Go, take advantage of subtests to clearly delineate between assertions on the occasions where you need to. This is also a handy technique to separate assertions on behaviour vs implementation detail.

For other tests where setup or execution time may be a constraint (e.g an acceptance test driving a web browser), you need to weigh up the pros and cons of slightly trickier to debug tests against test execution time.

## Not listening to your tests

[Dave Farley in his video "When TDD goes wrong"](https://www.youtube.com/watch?v=UWtEVKVPBQ0&feature=youtu.be) points out,

> TDD gives you the fastest feedback possible on your design

From my own experience, a lot of developers are trying to practice TDD but frequently ignore the signals coming back to them from the TDD process. So they're still stuck with fragile, annoying systems, with a poor test suite.

Simply put, if testing your code is difficult, then _using_ your code is difficult too. Treat your tests as the first user of your code and then you'll see if your code is pleasant to work with or not.

I've emphasised this a lot in the book, and I'll say it again **listen to your tests**.

### Excessive setup, too many test doubles, etc.

Ever looked at a test with 20, 50, 100, 200 lines of setup code before anything interesting in the test happens? Do you then have to change the code and revisit the mess and wish you had a different career?

What are the signals here? _Listen_, complicated tests `==` complicated code. Why is your code complicated? Does it have to be?

- When you have lots of test doubles in your tests, that means the code you're testing has lots of dependencies - which means your design needs work.
- If your test is reliant on setting up various interactions with mocks, that means your code is making lots of interactions with its dependencies. Ask yourself whether these interactions could be simpler.

#### Leaky interfaces

If you have declared an `interface` that has many methods, that points to a leaky abstraction. Think about how you could define that collaboration with a more consolidated set of methods, ideally one.

#### Interface pollution

As a Go proverb says, *the bigger the interface, the weaker the abstraction*. If you expose a huge interface to the users of your package, you force them to create in their tests a stub/mock that matches the entire API, providing an implementation also for methods they do not use (sometimes, they just panic to make clear that they should not be used). This situation is an anti-pattern known as [interface pollution](https://rakyll.org/interface-pollution/) and this is the reason why the standard library offers you just tiny little interfaces. 

Instead, you should expose from your package a bare struct with all relevant methods exported, leaving to the clients of your API the freedom to declare their own interfaces abstracting over the subset of the methods they need: e.g [go-redis](https://github.com/redis/go-redis) exposes a struct (`redis.Client`) to the API clients.

Generally speaking, you should expose an interface to the clients only when:
- the interface consists of a small and coherent set of functions.
- the interface and its implementation need to be decoupled (e.g. because users can choose among multiple implementations or they need to mock an external dependency).

#### Think about the types of test doubles you use

- Mocks are sometimes helpful, but they're extremely powerful and therefore easy to misuse. Try giving yourself the constraint of using stubs instead.
- Verifying implementation detail with spies is sometimes helpful, but try to avoid it. Remember your implementation detail is usually not important, and you don't want your tests coupled to them if possible. Look to couple your tests to **useful behaviour rather than incidental details**.
- [Read my posts on naming test doubles](https://quii.dev/Start_naming_your_test_doubles_correctly) if the taxonomy of test doubles is a little unclear

#### Consolidate dependencies

Here is some code for a `http.HandlerFunc` to handle new user registrations for a website.

```go
type User struct {
	// Some user fields
}

type UserStore interface {
	CheckEmailExists(email string) (bool, error)
	StoreUser(newUser User) error
}

type Emailer interface {
	SendEmail(to User, body string, subject string) error
}

func NewRegistrationHandler(userStore UserStore, emailer Emailer) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		// extract out the user from the request body (handle error)
		// check user exists (handle duplicates, errors)
		// store user (handle errors)
		// compose and send confirmation email (handle error)
		// if we got this far, return 2xx response
	}
}
```

At first pass it's reasonable to say the design isn't so bad. It only has 2 dependencies!

Re-evaluate the design by considering the handler's responsibilities:

- Parse the request body into a `User` :white_check_mark:
- Use `UserStore` to check if the user exists :question:
- Use `UserStore` to store the user :question:
- Compose an email :question:
- Use `Emailer` to send the email :question:
- Return an appropriate http response, depending on success, errors, etc :white_check_mark:

To exercise this code, you're going to have to write many tests with varying degrees of test double setups, spies, etc

- What if the requirements expand? Translations for the emails? Sending an SMS confirmation too? Does it make sense to you that you have to change a HTTP handler to accommodate this change?
- Does it feel right that the important rule of "we should send an email" resides within a HTTP handler?
    - Why do you have to go through the ceremony of creating HTTP requests and reading responses to verify that rule?

**Listen to your tests**. Writing tests for this code in a TDD fashion should quickly make you feel uncomfortable (or at least, make the lazy developer in you be annoyed). If it feels painful, stop and think.

What if the design was like this instead?

```go
type UserService interface {
	Register(newUser User) error
}

func NewRegistrationHandler(userService UserService) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		// parse user
		// register user
		// check error, send response
	}
}
```

- Simple to test the handler ✅
- Changes to the rules around registration are isolated away from HTTP, so they are also simpler to test ✅

## Violating encapsulation

Encapsulation is very important. There's a reason we don't make everything in a package exported (or public). We want coherent APIs with a small surface area to avoid tight coupling.

People will sometimes be tempted to make a function or method public in order to test something. By doing this you make your design worse and send confusing messages to maintainers and users of your code.

A result of this can be developers trying to debug a test and then eventually realising the function being tested is _only called from tests_. Which is obviously **a terrible outcome, and a waste of time**.

In Go, consider your default position for writing tests as _from the perspective of a consumer of your package_. You can make this a compile-time constraint by having your tests live in a test package e.g `package gocoin_test`. If you do this, you'll only have access to the exported members of the package so it won't be possible to couple yourself to implementation detail.

## Complicated table tests

Table tests are a great way of exercising a number of different scenarios when the test setup is the same, and you only wish to vary the inputs.

_But_ they can be messy to read and understand when you try to shoehorn other kinds of tests under the name of having one, glorious table.

```go
cases := []struct {
	X                int
	Y                int
	Z                int
	err              error
	IsFullMoon       bool
	IsLeapYear       bool
	AtWarWithEurasia bool
}{}
```

**Don't be afraid to break out of your table and write new tests** rather than adding new fields and booleans to the table `struct`.

A thing to bear in mind when writing software is,

> [Simple is not easy](https://www.infoq.com/presentations/Simple-Made-Easy/)

"Just" adding a field to a table might be easy, but it can make things far from simple.

## Summary

Most problems with unit tests can normally be traced to:

- Developers not following the TDD process
- Poor design

So, learn about good software design!

The good news is TDD can help you _improve your design skills_ because as stated in the beginning:

**TDD's main purpose is to provide feedback on your design.** For the millionth time, listen to your tests, they are reflecting your design back at you.

Be honest about the quality of your tests by listening to the feedback they give you, and you'll become a better developer for it.


================================================
FILE: app-intro.md
================================================
# Build an application

Now that you have hopefully digested the _Go Fundamentals_ section you have a solid grounding of a majority of Go's language features and how to do TDD.

This next section will involve building an application.

Each chapter will iterate on the previous one, expanding the application's functionality as our product owner dictates.

New concepts will be introduced to help facilitate writing great code but most of the new material will be learning what can be accomplished from Go's standard library.

By the end of this, you should have a strong grasp as to how to iteratively write an application in Go, backed by tests.

- [HTTP server](http-server.md) - We will create an application which listens to HTTP requests and responds to them.
- [JSON, routing and embedding](json.md) - We will make our endpoints return JSON and explore how to do routing.
- [IO and sorting](io.md) - We will persist and read our data from disk and we'll cover sorting data.
- [Command line & project structure](command-line.md) - Support multiple applications from one code base and read input from command line.
- [Time](time.md) - using the `time` package to schedule activities.
- [WebSockets](websockets.md) - learn how to write and test a server that uses WebSockets.


================================================
FILE: arrays/v1/sum.go
================================================
package main

// Sum calculates the total from an array of numbers.
func Sum(numbers [5]int) int {
	sum := 0
	for i := 0; i < 5; i++ {
		sum += numbers[i]
	}
	return sum
}


================================================
FILE: arrays/v1/sum_test.go
================================================
package main

import "testing"

func TestSum(t *testing.T) {

	numbers := [5]int{1, 2, 3, 4, 5}

	got := Sum(numbers)
	want := 15

	if want != got {
		t.Errorf("got %d want %d given, %v", got, want, numbers)
	}
}


================================================
FILE: arrays/v2/sum.go
================================================
package main

// Sum calculates the total from an array of numbers.
func Sum(numbers [5]int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}


================================================
FILE: arrays/v2/sum_test.go
================================================
package main

import "testing"

func TestSum(t *testing.T) {

	numbers := [5]int{1, 2, 3, 4, 5}

	got := Sum(numbers)
	want := 15

	if got != want {
		t.Errorf("got %d want %d given, %v", got, want, numbers)
	}
}


================================================
FILE: arrays/v3/sum.go
================================================
package main

// Sum calculates the total from a slice of numbers.
func Sum(numbers []int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}


================================================
FILE: arrays/v3/sum_test.go
================================================
package main

import "testing"

func TestSum(t *testing.T) {

	t.Run("collections of any size", func(t *testing.T) {

		numbers := []int{1, 2, 3}

		got := Sum(numbers)
		want := 6

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

}


================================================
FILE: arrays/v4/sum.go
================================================
package main

// Sum calculates the total from a slice of numbers.
func Sum(numbers []int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}

// SumAll calculates the respective sums of every slice passed in.
func SumAll(numbersToSum ...[]int) []int {
	lengthOfNumbers := len(numbersToSum)
	sums := make([]int, lengthOfNumbers)

	for i, numbers := range numbersToSum {
		sums[i] = Sum(numbers)
	}

	return sums
}


================================================
FILE: arrays/v4/sum_test.go
================================================
package main

import (
	"slices"
	"testing"
)

func TestSum(t *testing.T) {

	t.Run("collections of any size", func(t *testing.T) {

		numbers := []int{1, 2, 3}

		got := Sum(numbers)
		want := 6

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})
}

func TestSumAll(t *testing.T) {

	got := SumAll([]int{1, 2}, []int{0, 9})
	want := []int{3, 9}

	if !slices.Equal(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}


================================================
FILE: arrays/v5/sum.go
================================================
package main

// Sum calculates the total from a slice of numbers.
func Sum(numbers []int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}

// SumAll calculates the respective sums of every slice passed in.
func SumAll(numbersToSum ...[]int) []int {
	var sums []int
	for _, numbers := range numbersToSum {
		sums = append(sums, Sum(numbers))
	}

	return sums
}


================================================
FILE: arrays/v5/sum_test.go
================================================
package main

import (
	"slices"
	"testing"
)

func TestSum(t *testing.T) {

	t.Run("collections of any size", func(t *testing.T) {

		numbers := []int{1, 2, 3}

		got := Sum(numbers)
		want := 6

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

}

func TestSumAll(t *testing.T) {

	got := SumAll([]int{1, 2}, []int{0, 9})
	want := []int{3, 9}

	if !slices.Equal(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}


================================================
FILE: arrays/v6/sum.go
================================================
package main

// Sum calculates the total from a slice of numbers.
func Sum(numbers []int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}

// SumAllTails calculates the respective sums of every slice passed in.
func SumAllTails(numbersToSum ...[]int) []int {
	var sums []int
	for _, numbers := range numbersToSum {
		tail := numbers[1:]
		sums = append(sums, Sum(tail))
	}

	return sums
}


================================================
FILE: arrays/v6/sum_test.go
================================================
package main

import (
	"slices"
	"testing"
)

func TestSum(t *testing.T) {

	t.Run("collections of any size", func(t *testing.T) {

		numbers := []int{1, 2, 3}

		got := Sum(numbers)
		want := 6

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

}

func TestSumAllTails(t *testing.T) {

	got := SumAllTails([]int{1, 2}, []int{0, 9})
	want := []int{2, 9}

	if !slices.Equal(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}


================================================
FILE: arrays/v7/sum.go
================================================
package main

// Sum calculates the total from a slice of numbers.
func Sum(numbers []int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}

// SumAllTails calculates the sums of all but the first number given a collection of slices.
func SumAllTails(numbersToSum ...[]int) []int {
	var sums []int
	for _, numbers := range numbersToSum {
		if len(numbers) == 0 {
			sums = append(sums, 0)
		} else {
			tail := numbers[1:]
			sums = append(sums, Sum(tail))
		}
	}

	return sums
}


================================================
FILE: arrays/v7/sum_test.go
================================================
package main

import (
	"slices"
	"testing"
)

func TestSum(t *testing.T) {

	t.Run("collections of any size", func(t *testing.T) {

		numbers := []int{1, 2, 3}

		got := Sum(numbers)
		want := 6

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

}

func TestSumAllTails(t *testing.T) {

	checkSums := func(t *testing.T, got, want []int) {
		if !slices.Equal(got, want) {
			t.Errorf("got %v want %v", got, want)
		}
	}

	t.Run("make the sums of tails of", func(t *testing.T) {
		got := SumAllTails([]int{1, 2}, []int{0, 9})
		want := []int{2, 9}
		checkSums(t, got, want)
	})

	t.Run("safely sum empty slices", func(t *testing.T) {
		got := SumAllTails([]int{}, []int{3, 4, 5})
		want := []int{0, 9}
		checkSums(t, got, want)
	})

}


================================================
FILE: arrays/v8/assert.go
================================================
package main

import "testing"

func AssertEqual[T comparable](t *testing.T, got, want T) {
	t.Helper()
	if got != want {
		t.Errorf("got %v, want %v", got, want)
	}
}

func AssertNotEqual[T comparable](t *testing.T, got, want T) {
	t.Helper()
	if got == want {
		t.Errorf("didn't want %v", got)
	}
}

func AssertTrue(t *testing.T, got bool) {
	t.Helper()
	if !got {
		t.Errorf("got %v, want true", got)
	}
}

func AssertFalse(t *testing.T, got bool) {
	t.Helper()
	if got {
		t.Errorf("got %v, want false", got)
	}
}


================================================
FILE: arrays/v8/bad_bank.go
================================================
package main

type Transaction struct {
	From string
	To   string
	Sum  float64
}

func NewTransaction(from, to Account, sum float64) Transaction {
	return Transaction{From: from.Name, To: to.Name, Sum: sum}
}

type Account struct {
	Name    string
	Balance float64
}

func NewBalanceFor(account Account, transactions []Transaction) Account {
	return Reduce(
		transactions,
		applyTransaction,
		account,
	)
}

func applyTransaction(a Account, transaction Transaction) Account {
	if transaction.From == a.Name {
		a.Balance -= transaction.Sum
	}
	if transaction.To == a.Name {
		a.Balance += transaction.Sum
	}
	return a
}


================================================
FILE: arrays/v8/bad_bank_test.go
================================================
package main

import "testing"

func TestBadBank(t *testing.T) {
	var (
		riya  = Account{Name: "Riya", Balance: 100}
		chris = Account{Name: "Chris", Balance: 75}
		adil  = Account{Name: "Adil", Balance: 200}

		transactions = []Transaction{
			NewTransaction(chris, riya, 100),
			NewTransaction(adil, chris, 25),
		}
	)

	newBalanceFor := func(account Account) float64 {
		return NewBalanceFor(account, transactions).Balance
	}

	AssertEqual(t, newBalanceFor(riya), 200)
	AssertEqual(t, newBalanceFor(chris), 0)
	AssertEqual(t, newBalanceFor(adil), 175)
}


================================================
FILE: arrays/v8/collection_fun.go
================================================
package main

func Find[A any](items []A, predicate func(A) bool) (value A, found bool) {
	for _, v := range items {
		if predicate(v) {
			return v, true
		}
	}
	return
}

func Reduce[A, B any](collection []A, f func(B, A) B, initialValue B) B {
	var result = initialValue
	for _, x := range collection {
		result = f(result, x)
	}
	return result
}


================================================
FILE: arrays/v8/sum.go
================================================
package main

// Sum calculates the total from a slice of numbers.
func Sum(numbers []int) int {
	add := func(acc, x int) int { return acc + x }
	return Reduce(numbers, add, 0)
}

// SumAllTails calculates the sums of all but the first number given a collection of slices.
func SumAllTails(numbers ...[]int) []int {
	sumTail := func(acc, x []int) []int {
		if len(x) == 0 {
			return append(acc, 0)
		} else {
			tail := x[1:]
			return append(acc, Sum(tail))
		}
	}

	return Reduce(numbers, sumTail, []int{})
}


================================================
FILE: arrays/v8/sum_test.go
================================================
package main

import (
	"slices"
	"strings"
	"testing"
)

func TestSum(t *testing.T) {

	t.Run("collections of any size", func(t *testing.T) {

		numbers := []int{1, 2, 3}

		got := Sum(numbers)
		want := 6

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

}

func TestSumAllTails(t *testing.T) {

	checkSums := func(t *testing.T, got, want []int) {
		if !slices.Equal(got, want) {
			t.Errorf("got %v want %v", got, want)
		}
	}

	t.Run("make the sums of tails of", func(t *testing.T) {
		got := SumAllTails([]int{1, 2}, []int{0, 9})
		want := []int{2, 9}
		checkSums(t, got, want)
	})

	t.Run("safely sum empty slices", func(t *testing.T) {
		got := SumAllTails([]int{}, []int{3, 4, 5})
		want := []int{0, 9}
		checkSums(t, got, want)
	})

}

func TestReduce(t *testing.T) {
	t.Run("multiplication of all elements", func(t *testing.T) {
		multiply := func(x, y int) int {
			return x * y
		}

		AssertEqual(t, Reduce([]int{1, 2, 3}, multiply, 1), 6)
	})

	t.Run("concatenate strings", func(t *testing.T) {
		concatenate := func(x, y string) string {
			return x + y
		}

		AssertEqual(t, Reduce([]string{"a", "b", "c"}, concatenate, ""), "abc")
	})
}

func TestFind(t *testing.T) {
	t.Run("find first even number", func(t *testing.T) {
		numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

		firstEvenNumber, found := Find(numbers, func(x int) bool {
			return x%2 == 0
		})
		AssertTrue(t, found)
		AssertEqual(t, firstEvenNumber, 2)
	})

	type Person struct {
		Name string
	}

	t.Run("Find the best programmer", func(t *testing.T) {
		people := []Person{
			Person{Name: "Kent Beck"},
			Person{Name: "Martin Fowler"},
			Person{Name: "Chris James"},
		}

		king, found := Find(people, func(p Person) bool {
			return strings.Contains(p.Name, "Chris")
		})

		AssertTrue(t, found)
		AssertEqual(t, king, Person{Name: "Chris James"})
	})
}


================================================
FILE: arrays-and-slices.md
================================================
# Arrays and slices

**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main/arrays)**

Arrays allow you to store multiple elements of the same type in a variable in
a particular order.

When you have arrays, it is very common to have to iterate over them. So let's
use [our new-found knowledge of `for`](iteration.md) to make a `Sum` function. `Sum` will
take an array of numbers and return the total.

Let's use our TDD skills

## Write the test first

Create a new folder to work in. Create a new file called `sum_test.go` and insert the following:

```go
package main

import "testing"

func TestSum(t *testing.T) {

	numbers := [5]int{1, 2, 3, 4, 5}

	got := Sum(numbers)
	want := 15

	if got != want {
		t.Errorf("got %d want %d given, %v", got, want, numbers)
	}
}
```

Arrays have a _fixed capacity_ which you define when you declare the variable.
We can initialize an array in two ways:

* \[N\]type{value1, value2, ..., valueN} e.g. `numbers := [5]int{1, 2, 3, 4, 5}`
* \[...\]type{value1, value2, ..., valueN} e.g. `numbers := [...]int{1, 2, 3, 4, 5}`

It is sometimes useful to also print the inputs to the function in the error message.
Here, we are using the `%v` placeholder to print the "default" format, which works well for arrays.

[Read more about the format strings](https://golang.org/pkg/fmt/)

## Try to run the test

If you had initialized go mod with `go mod init main` you will be presented with an error
`_testmain.go:13:2: cannot import "main"`. This is because according to common practice,
package main will only contain integration of other packages and not unit-testable code and
hence Go will not allow you to import a package with name `main`.

To fix this, you can rename the main module in `go.mod` to any other name.

Once the above error is fixed, if you run `go test` the compiler will fail with the familiar
`./sum_test.go:10:15: undefined: Sum` error. Now we can proceed with writing the actual method
to be tested.

## Write the minimal amount of code for the test to run and check the failing test output

In `sum.go`

```go
package main

func Sum(numbers [5]int) int {
	return 0
}
```

Your test should now fail with _a clear error message_

`sum_test.go:13: got 0 want 15 given, [1 2 3 4 5]`

## Write enough code to make it pass

```go
func Sum(numbers [5]int) int {
	sum := 0
	for i := 0; i < 5; i++ {
		sum += numbers[i]
	}
	return sum
}
```

To get the value out of an array at a particular index, just use `array[index]`
syntax. In this case, we are using `for` to iterate 5 times to work through the
array and add each item onto `sum`.

## Refactor

Let's introduce [`range`](https://gobyexample.com/range) to help clean up our code

```go
func Sum(numbers [5]int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}
```

`range` lets you iterate over an array. On each iteration, `range` returns two values - the index and the value.
We are choosing to ignore the index value by using `_` [blank identifier](https://golang.org/doc/effective_go.html#blank).

### Arrays and their type

An interesting property of arrays is that the size is encoded in its type. If you try
to pass an `[4]int` into a function that expects `[5]int`, it won't compile.
They are different types so it's just the same as trying to pass a `string` into
a function that wants an `int`.

You may be thinking it's quite cumbersome that arrays have a fixed length, and most
of the time you probably won't be using them!

Go has _slices_ which do not encode the size of the collection and instead can
have any size.

The next requirement will be to sum collections of varying sizes.

## Write the test first

We will now use the [slice type][slice] which allows us to have collections of
any size. The syntax is very similar to arrays, you just omit the size when
declaring them

`mySlice := []int{1,2,3}` rather than `myArray := [3]int{1,2,3}`

```go
func TestSum(t *testing.T) {

	t.Run("collection of 5 numbers", func(t *testing.T) {
		numbers := [5]int{1, 2, 3, 4, 5}

		got := Sum(numbers)
		want := 15

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

	t.Run("collection of any size", func(t *testing.T) {
		numbers := []int{1, 2, 3}

		got := Sum(numbers)
		want := 6

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

}
```

## Try and run the test

This does not compile

`./sum_test.go:22:13: cannot use numbers (type []int) as type [5]int in argument to Sum`

## Write the minimal amount of code for the test to run and check the failing test output

The problem here is we can either

* Break the existing API by changing the argument to `Sum` to be a slice rather
  than an array. When we do this, we will potentially ruin
  someone's day because our _other_ test will no longer compile!
* Create a new function

In our case, no one else is using our function, so rather than having two functions to maintain, let's have just one.

```go
func Sum(numbers []int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}
```

If you try to run the tests they will still not compile, you will have to change the first test to pass in a slice rather than an array.

## Write enough code to make it pass

It turns out that fixing the compiler problems were all we need to do here and the tests pass!

## Refactor

We already refactored `Sum` - all we did was replace arrays with slices, so no extra changes are required.
Remember that we must not neglect our test code in the refactoring stage - we can further improve our `Sum` tests.

```go
func TestSum(t *testing.T) {

	t.Run("collection of 5 numbers", func(t *testing.T) {
		numbers := []int{1, 2, 3, 4, 5}

		got := Sum(numbers)
		want := 15

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

	t.Run("collection of any size", func(t *testing.T) {
		numbers := []int{1, 2, 3}

		got := Sum(numbers)
		want := 6

		if got != want {
			t.Errorf("got %d want %d given, %v", got, want, numbers)
		}
	})

}
```

It is important to question the value of your tests. It should not be a goal to
have as many tests as possible, but rather to have as much _confidence_ as
possible in your code base. Having too many tests can turn in to a real problem
and it just adds more overhead in maintenance. **Every test has a cost**.

In our case, you can see that having two tests for this function is redundant.
If it works for a slice of one size it's very likely it'll work for a slice of
any size \(within reason\).

Go's built-in testing toolkit features a [coverage tool](https://blog.golang.org/cover).
Whilst striving for 100% coverage should not be your end goal, the coverage tool can help
identify areas of your code not covered by tests. If you have been strict with TDD,
it's quite likely you'll have close to 100% coverage anyway.

Try running

`go test -cover`

You should see

```bash
PASS
coverage: 100.0% of statements
```

Now delete one of the tests and check the coverage again.

Now that we are happy we have a well-tested function you should commit your
great work before taking on the next challenge.

We need a new function called `SumAll` which will take a varying number of
slices, returning a new slice containing the totals for each slice passed in.

For example

`SumAll([]int{1,2}, []int{0,9})` would return `[]int{3, 9}`

or

`SumAll([]int{1,1,1})` would return `[]int{3}`

## Write the test first

```go
func TestSumAll(t *testing.T) {

	got := SumAll([]int{1, 2}, []int{0, 9})
	want := []int{3, 9}

	if got != want {
		t.Errorf("got %v want %v", got, want)
	}
}
```

## Try and run the test

`./sum_test.go:23:9: undefined: SumAll`

## Write the minimal amount of code for the test to run and check the failing test output

We need to define `SumAll` according to what our test wants.

Go can let you write [_variadic functions_](https://gobyexample.com/variadic-functions) that can take a variable number of arguments.

```go
func SumAll(numbersToSum ...[]int) []int {
	return nil
}
```

This is valid, but our tests still won't compile!

`./sum_test.go:26:9: invalid operation: got != want (slice can only be compared to nil)`

Go does not let you use equality operators with slices. You _could_ write
a function to iterate over each `got` and `want` slice and check their values,
but what if we had a more convenient way to do this?

From Go 1.21, [slices](https://pkg.go.dev/slices#pkg-overview) standard package is available, which has [slices.Equal](https://pkg.go.dev/slices#Equal) function to do a simple shallow compare on slices, where you don't need to worry about the types like the above case.
Note that this function expects the elements to be [comparable](https://pkg.go.dev/builtin#comparable).
So, it can't be applied to slices with non-comparable elements like 2D slices.

Let's go ahead and put this into practice!

```go
func TestSumAll(t *testing.T) {

	got := SumAll([]int{1, 2}, []int{0, 9})
	want := []int{3, 9}

	if !slices.Equal(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}
```

You should have test output like the following:
`sum_test.go:30: got [] want [3 9]`

## Write enough code to make it pass

What we need to do is iterate over the varargs, calculate the sum using our
existing `Sum` function, then add it to the slice we will return

```go
func SumAll(numbersToSum ...[]int) []int {
	lengthOfNumbers := len(numbersToSum)
	sums := make([]int, lengthOfNumbers)

	for i, numbers := range numbersToSum {
		sums[i] = Sum(numbers)
	}

	return sums
}
```

Lots of new things to learn!

There's a new way to create a slice. `make` allows you to create a slice with
a starting capacity of the `len` of the `numbersToSum` we need to work through. The length of a slice is the number of elements it holds `len(mySlice)`, while the capacity is the number of elements it can hold in the underlying array `cap(mySlice)`, e.g., `make([]int, 0, 5)` creates a slice with length 0 and capacity 5.

You can index slices like arrays with `mySlice[N]` to get the value out or
assign it a new value with `=`

The tests should now pass.

## Refactor

As mentioned, slices have a capacity. If you have a slice with a capacity of
2 and try to do `mySlice[10] = 1` you will get a _runtime_ error.

However, you can use the `append` function which takes a slice and a new value,
then returns a new slice with all the items in it.

```go
func SumAll(numbersToSum ...[]int) []int {
	var sums []int
	for _, numbers := range numbersToSum {
		sums = append(sums, Sum(numbers))
	}

	return sums
}
```

In this implementation, we are worrying less about capacity. We start with an
empty slice `sums` and append to it the result of `Sum` as we work through the varargs.

Our next requirement is to change `SumAll` to `SumAllTails`, where it will
calculate the totals of the "tails" of each slice. The tail of a collection is
all items in the collection except the first one \(the "head"\).

## Write the test first

```go
func TestSumAllTails(t *testing.T) {
	got := SumAllTails([]int{1, 2}, []int{0, 9})
	want := []int{2, 9}

	if !reflect.DeepEqual(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}
```

## Try and run the test

`./sum_test.go:26:9: undefined: SumAllTails`

## Write the minimal amount of code for the test to run and check the failing test output

Rename the function to `SumAllTails` and re-run the test

`sum_test.go:30: got [3 9] want [2 9]`

## Write enough code to make it pass

```go
func SumAllTails(numbersToSum ...[]int) []int {
	var sums []int
	for _, numbers := range numbersToSum {
		tail := numbers[1:]
		sums = append(sums, Sum(tail))
	}

	return sums
}
```

Slices can be sliced! The syntax is `slice[low:high]`. If you omit the value on
one of the sides of the `:` it captures everything to that side of it. In our
case, we are saying "take from 1 to the end" with `numbers[1:]`. You may wish to
spend some time writing other tests around slices and experiment with the
slice operator to get more familiar with it.

## Refactor

Not a lot to refactor this time.

What do you think would happen if you passed in an empty slice into our
function? What is the "tail" of an empty slice? What happens when you tell Go to
capture all elements from `myEmptySlice[1:]`?

## Write the test first

```go
func TestSumAllTails(t *testing.T) {

	t.Run("make the sums of some slices", func(t *testing.T) {
		got := SumAllTails([]int{1, 2}, []int{0, 9})
		want := []int{2, 9}

		if !reflect.DeepEqual(got, want) {
			t.Errorf("got %v want %v", got, want)
		}
	})

	t.Run("safely sum empty slices", func(t *testing.T) {
		got := SumAllTails([]int{}, []int{3, 4, 5})
		want := []int{0, 9}

		if !reflect.DeepEqual(got, want) {
			t.Errorf("got %v want %v", got, want)
		}
	})

}
```

## Try and run the test

```text
panic: runtime error: slice bounds out of range [recovered]
    panic: runtime error: slice bounds out of range
```

Oh no! It's important to note that while the test _has compiled_, it _has a runtime error_.

Compile time errors are our friend because they help us write software that works,
runtime errors are our enemies because they affect our users.

## Write enough code to make it pass

```go
func SumAllTails(numbersToSum ...[]int) []int {
	var sums []int
	for _, numbers := range numbersToSum {
		if len(numbers) == 0 {
			sums = append(sums, 0)
		} else {
			tail := numbers[1:]
			sums = append(sums, Sum(tail))
		}
	}

	return sums
}
```

## Refactor

Our tests have some repeated code around the assertions again, so let's extract those into a function.

```go
func TestSumAllTails(t *testing.T) {

	checkSums := func(t testing.TB, got, want []int) {
		t.Helper()
		if !reflect.DeepEqual(got, want) {
			t.Errorf("got %v want %v", got, want)
		}
	}

	t.Run("make the sums of tails of", func(t *testing.T) {
		got := SumAllTails([]int{1, 2}, []int{0, 9})
		want := []int{2, 9}
		checkSums(t, got, want)
	})

	t.Run("safely sum empty slices", func(t *testing.T) {
		got := SumAllTails([]int{}, []int{3, 4, 5})
		want := []int{0, 9}
		checkSums(t, got, want)
	})

}
```

We could've created a new function `checkSums` like we normally do, but in this case, we're showing a new technique, assigning a function to a variable. It might look strange but, it's no different to assigning a variable to a `string`, or an `int`, functions in effect are values too.

It's not shown here, but this technique can be useful when you want to bind a function to other local variables in "scope" (e.g between some `{}`). It also allows you to reduce the surface area of your API.

By defining this function inside the test, it cannot be used by other functions in this package. Hiding variables and functions that don't need to be exported is an important design consideration.

A handy side-effect of this is this adds a little type-safety to our code. If
a developer mistakenly adds a new test with `checkSums(t, got, "dave")` the compiler
will stop them in their tracks.

```bash
$ go test
./sum_test.go:52:21: cannot use "dave" (type string) as type []int in argument to checkSums
```

## Wrapping up

We have covered

* Arrays
* Slices
  * The various ways to make them
  * How they have a _fixed_ capacity but you can create new slices from old ones
    using `append`
  * How to slice, slices!
* `len` to get the length of an array or slice
* Test coverage tool
* `reflect.DeepEqual` and why it's useful but can reduce the type-safety of your code

We've used slices and arrays with integers but they work with any other type
too, including arrays/slices themselves. So you can declare a variable of
`[][]string` if you need to.

[Check out the Go blog post on slices][blog-slice] for an in-depth look into
slices. Try writing more tests to solidify what you learn from reading it.

Another handy way to experiment with Go other than writing tests is the Go
playground. You can try most things out and you can easily share your code if
you need to ask questions. [I have made a go playground with a slice in it for you to experiment with.](https://play.golang.org/p/ICCWcRGIO68)

[Here is an example](https://play.golang.org/p/bTrRmYfNYCp) of slicing an array
and how changing the slice affects the original array; but a "copy" of the slice
will not affect the original array.
[Another example](https://play.golang.org/p/Poth8JS28sc) of why it's a good idea
to make a copy of a slice after slicing a very large slice.

[for]: ../iteration.md#
[blog-slice]: https://blog.golang.org/go-slices-usage-and-internals
[deepEqual]: https://golang.org/pkg/reflect/#DeepEqual
[slice]: https://golang.org/doc/effective_go.html#slices


================================================
FILE: blogrenderer/post.go
================================================
package blogrenderer

import "strings"

// Post is a representation of a post
type Post struct {
	Title, Description, Body string
	Tags                     []string
}

// SanitisedTitle returns the title of the post with spaces replaced by dashes for pleasant URLs
func (p Post) SanitisedTitle() string {
	return strings.ToLower(strings.Replace(p.Title, " ", "-", -1))
}


================================================
FILE: blogrenderer/renderer.go
================================================
package blogrenderer

import (
	"embed"
	"github.com/gomarkdown/markdown"
	"github.com/gomarkdown/markdown/parser"
	"html/template"
	"io"
)

var (
	//go:embed "templates/*"
	postTemplates embed.FS
)

// PostRenderer renders data into HTML
type PostRenderer struct {
	templ    *template.Template
	mdParser *parser.Parser
}

// NewPostRenderer creates a new PostRenderer
func NewPostRenderer() (*PostRenderer, error) {
	templ, err := template.ParseFS(postTemplates, "templates/*.gohtml")
	if err != nil {
		return nil, err
	}

	extensions := parser.CommonExtensions | parser.AutoHeadingIDs
	parser := parser.NewWithExtensions(extensions)

	return &PostRenderer{templ: templ, mdParser: parser}, nil
}

// Render renders post into HTML
func (r *PostRenderer) Render(w io.Writer, p Post) error {
	return r.templ.ExecuteTemplate(w, "blog.gohtml", newPostVM(p, r))
}

// RenderIndex creates an HTML index page given a collection of posts
func (r *PostRenderer) RenderIndex(w io.Writer, posts []Post) error {
	return r.templ.ExecuteTemplate(w, "index.gohtml", posts)
}

type postViewModel struct {
	Post
	HTMLBody template.HTML
}

func newPostVM(p Post, r *PostRenderer) postViewModel {
	vm := postViewModel{Post: p}
	vm.HTMLBody = template.HTML(markdown.ToHTML([]byte(p.Body), r.mdParser, nil))
	return vm
}


================================================
FILE: blogrenderer/renderer_test.TestRender.it_converts_a_single_post_into_HTML.approved.txt
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <title>My amazing blog!</title>
    <meta charset="UTF-8"/>
    <meta name="description" content="Wow, like and subscribe, it really helps the channel guys" lang="en"/>
</head>
<body>
<nav role="navigation">
    <div>
        <h1>Budding Gopher's blog</h1>
        <ul>
            <li><a href="/">home</a></li>
            <li><a href="about">about</a></li>
            <li><a href="archive">archive</a></li>
        </ul>
    </div>
</nav>
<main>

<h1>hello world</h1>

<p>This is a description</p>

Tags: <ul><li>go</li><li>tdd</li></ul>
<h1 id="first-recipe">First recipe!</h1>

<p>Welcome to my <strong>amazing blog</strong>. I am going to write about my family recipes, and make sure I write a long, irrelevant and boring story about my family before you get to the actual instructions.</p>


</main>
<footer>
    <ul>
        <li><a href="https://twitter.com/quii">Twitter</a></li>
        <li><a href="https://github.com/quii">GitHub</a></li>
    </ul>
</footer>
</body>
</html>



================================================
FILE: blogrenderer/renderer_test.TestRender.it_renders_an_index_of_posts.approved.txt
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <title>My amazing blog!</title>
    <meta charset="UTF-8"/>
    <meta name="description" content="Wow, like and subscribe, it really helps the channel guys" lang="en"/>
</head>
<body>
<nav role="navigation">
    <div>
        <h1>Budding Gopher's blog</h1>
        <ul>
            <li><a href="/">home</a></li>
            <li><a href="about">about</a></li>
            <li><a href="archive">archive</a></li>
        </ul>
    </div>
</nav>
<main>

<ol><li><a href="/post/hello-world">Hello World</a></li><li><a href="/post/hello-world-2">Hello World 2</a></li></ol>

</main>
<footer>
    <ul>
        <li><a href="https://twitter.com/quii">Twitter</a></li>
        <li><a href="https://github.com/quii">GitHub</a></li>
    </ul>
</footer>
</body>
</html>



================================================
FILE: blogrenderer/renderer_test.go
================================================
package blogrenderer_test

import (
	"bytes"
	approvals "github.com/approvals/go-approval-tests"
	"github.com/quii/learn-go-with-tests/blogrenderer"
	"io"
	"testing"
)

func TestRender(t *testing.T) {
	var (
		aPost = blogrenderer.Post{
			Title: "hello world",
			Body: `# First recipe!
Welcome to my **amazing blog**. I am going to write about my family recipes, and make sure I write a long, irrelevant and boring story about my family before you get to the actual instructions.`,
			Description: "This is a description",
			Tags:        []string{"go", "tdd"},
		}
	)

	postRenderer, err := blogrenderer.NewPostRenderer()

	if err != nil {
		t.Fatal(err)
	}

	t.Run("it converts a single post into HTML", func(t *testing.T) {
		buf := bytes.Buffer{}

		if err := postRenderer.Render(&buf, aPost); err != nil {
			t.Fatal(err)
		}

		approvals.VerifyString(t, buf.String())
	})

	t.Run("it renders an index of posts", func(t *testing.T) {
		buf := bytes.Buffer{}
		posts := []blogrenderer.Post{{Title: "Hello World"}, {Title: "Hello World 2"}}

		if err := postRenderer.RenderIndex(&buf, posts); err != nil {
			t.Fatal(err)
		}

		approvals.VerifyString(t, buf.String())
	})
}

func BenchmarkRender(b *testing.B) {
	var (
		aPost = blogrenderer.Post{
			Title:       "hello world",
			Body:        "This is a post",
			Description: "This is a description",
			Tags:        []string{"go", "tdd"},
		}
	)

	postRenderer, err := blogrenderer.NewPostRenderer()

	if err != nil {
		b.Fatal(err)
	}

	for b.Loop() {
		postRenderer.Render(io.Discard, aPost)
	}
}


================================================
FILE: blogrenderer/templates/blog.gohtml
================================================
{{template "top" .}}
<h1>{{.Title}}</h1>

<p>{{.Description}}</p>

Tags: <ul>{{range .Tags}}<li>{{.}}</li>{{end}}</ul>
{{.HTMLBody}}
{{template "bottom" .}}


================================================
FILE: blogrenderer/templates/bottom.gohtml
================================================
{{define "bottom"}}
</main>
<footer>
    <ul>
        <li><a href="https://twitter.com/quii">Twitter</a></li>
        <li><a href="https://github.com/quii">GitHub</a></li>
    </ul>
</footer>
</body>
</html>
{{end}}


================================================
FILE: blogrenderer/templates/index.gohtml
================================================
{{template "top" .}}
<ol>{{range .}}<li><a href="/post/{{.SanitisedTitle}}">{{.Title}}</a></li>{{end}}</ol>
{{template "bottom" .}}


================================================
FILE: blogrenderer/templates/top.gohtml
================================================
{{define "top"}}<!DOCTYPE html>
<html lang="en">
<head>
    <title>My amazing blog!</title>
    <meta charset="UTF-8"/>
    <meta name="description" content="Wow, like and subscribe, it really helps the channel guys" lang="en"/>
</head>
<body>
<nav role="navigation">
    <div>
        <h1>Budding Gopher's blog</h1>
        <ul>
            <li><a href="/">home</a></li>
            <li><a href="about">about</a></li>
            <li><a href="archive">archive</a></li>
        </ul>
    </div>
</nav>
<main>
{{end}}


================================================
FILE: book.json
================================================
{
    "structure": {
        "readme": "gb-readme.md"
    }
}


================================================
FILE: build.books.sh
================================================
#!/usr/bin/env bash

set -e

# safer separator for sed
sep=$'\001'

if [ -v GITHUB_REF_NAME ]; then
    sed "s${sep}%%FOOTER_VERSION%%${sep}${GITHUB_REF_NAME}${sep}" meta.tmpl.tex > meta.tex
else
    sed "s${sep}%%FOOTER_VERSION%%${sep}UNDEFINED VERSION${sep}" meta.tmpl.tex > meta.tex
fi

docker run --rm -v `pwd`:/data uppalabharath/pandoc-latex-cjk:latest --from=gfm+rebase_relative_paths -o learn-go-with-tests.pdf \
    -H meta.tex --pdf-engine=xelatex --variable urlcolor=blue --toc --toc-depth=1 \
    -B pdf-cover.tex \
    gb-readme.md \
    why.md \
    hello-world.md \
    integers.md \
    iteration.md \
    arrays-and-slices.md \
    structs-methods-and-interfaces.md \
    pointers-and-errors.md \
    maps.md \
    dependency-injection.md \
    mocking.md \
    concurrency.md \
    select.md \
    reflection.md \
    sync.md \
    context.md \
    roman-numerals.md \
    math.md \
    reading-files.md \
    html-templates.md \
    generics.md \
    revisiting-arrays-and-slices-with-generics.md \
    intro-to-acceptance-tests.md \
    scaling-acceptance-tests.md \
    working-without-mocks.md \
    refactoring-checklist.md \
    app-intro.md \
    http-server.md \
    json.md \
    io.md \
    command-line.md \
    time.md \
    websockets.md \
    os-exec.md \
    error-types.md \
    context-aware-reader.md \
    http-handlers-revisited.md \
    anti-patterns.md

docker run --rm -v `pwd`:/data pandoc/latex:latest --from=gfm+rebase_relative_paths --to=epub --file-scope title.txt -o learn-go-with-tests.epub --pdf-engine=xelatex --toc --toc-depth=1  \
    gb-readme.md \
    why.md \
    hello-world.md \
    integers.md \
    iteration.md \
    arrays-and-slices.md \
    structs-methods-and-interfaces.md \
    pointers-and-errors.md \
    maps.md \
    dependency-injection.md \
    mocking.md \
    concurrency.md \
    select.md \
    reflection.md \
    sync.md \
    context.md \
    roman-numerals.md \
    math.md \
    reading-files.md \
    html-templates.md \
    generics.md \
    revisiting-arrays-and-slices-with-generics.md \
    intro-to-acceptance-tests.md \
    scaling-acceptance-tests.md \
    working-without-mocks.md \
    refactoring-checklist.md \
    app-intro.md \
    http-server.md \
    json.md \
    io.md \
    command-line.md \
    time.md \
    websockets.md \
    os-exec.md \
    error-types.md \
    context-aware-reader.md \
    http-handlers-revisited.md \
    anti-patterns.md


================================================
FILE: build.sh
================================================
#!/usr/bin/env bash

set -e

go install github.com/client9/misspell/cmd/misspell@latest
go install github.com/po3rin/gofmtmd/cmd/gofmtmd@latest

ls *.md | xargs misspell -error

for md_file in ./*.md; do
    echo "formatting  file: $md_file"
    gofmtmd  "$md_file" -r
done

go test ./...
go vet ./...
go fmt ./...


================================================
FILE: command-line/v1/cmd/cli/main.go
================================================
package main

import "fmt"

func main() {
	fmt.Println("Let's play poker")
}


================================================
FILE: command-line/v1/cmd/webserver/main.go
================================================
package main

import (
	"github.com/quii/learn-go-with-tests/command-line/v1"
	"log"
	"net/http"
	"os"
)

const dbFileName = "game.db.json"

func main() {
	db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)

	if err != nil {
		log.Fatalf("problem opening %s %v", dbFileName, err)
	}

	store, err := poker.NewFileSystemPlayerStore(db)

	if err != nil {
		log.Fatalf("problem creating file system player store, %v ", err)
	}

	server := poker.NewPlayerServer(store)

	log.Fatal(http.ListenAndServe(":5000", server))
}


================================================
FILE: command-line/v1/file_system_store.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sort"
)

// FileSystemPlayerStore stores players in the filesystem.
type FileSystemPlayerStore struct {
	database *json.Encoder
	league   League
}

// NewFileSystemPlayerStore creates a FileSystemPlayerStore initialising the store if needed.
func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, error) {

	err := initialisePlayerDBFile(file)

	if err != nil {
		return nil, fmt.Errorf("problem initialising player db file, %v", err)
	}

	league, err := NewLeague(file)

	if err != nil {
		return nil, fmt.Errorf("problem loading player store from file %s, %v", file.Name(), err)
	}

	return &FileSystemPlayerStore{
		database: json.NewEncoder(&tape{file}),
		league:   league,
	}, nil
}

func initialisePlayerDBFile(file *os.File) error {
	file.Seek(0, io.SeekStart)

	info, err := file.Stat()

	if err != nil {
		return fmt.Errorf("problem getting file info from file %s, %v", file.Name(), err)
	}

	if info.Size() == 0 {
		file.Write([]byte("[]"))
		file.Seek(0, io.SeekStart)
	}

	return nil
}

// GetLeague returns the scores of all the players.
func (f *FileSystemPlayerStore) GetLeague() League {
	sort.Slice(f.league, func(i, j int) bool {
		return f.league[i].Wins > f.league[j].Wins
	})
	return f.league
}

// GetPlayerScore retrieves a player's score.
func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {

	player := f.league.Find(name)

	if player != nil {
		return player.Wins
	}

	return 0
}

// RecordWin will store a win for a player, incrementing wins if already known.
func (f *FileSystemPlayerStore) RecordWin(name string) {
	player := f.league.Find(name)

	if player != nil {
		player.Wins++
	} else {
		f.league = append(f.league, Player{name, 1})
	}

	f.database.Encode(f.league)
}


================================================
FILE: command-line/v1/file_system_store_test.go
================================================
package poker

import (
	"os"
	"testing"
)

func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
	t.Helper()

	tmpfile, err := os.CreateTemp("", "db")

	if err != nil {
		t.Fatalf("could not create temp file %v", err)
	}

	tmpfile.Write([]byte(initialData))

	removeFile := func() {
		os.Remove(tmpfile.Name())
	}

	return tmpfile, removeFile
}

func TestFileSystemStore(t *testing.T) {

	t.Run("league sorted", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		got := store.GetLeague()

		want := []Player{
			{"Chris", 33},
			{"Cleo", 10},
		}

		assertLeague(t, got, want)

		// read again
		got = store.GetLeague()
		assertLeague(t, got, want)
	})

	t.Run("get player score", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		got := store.GetPlayerScore("Chris")
		want := 33
		assertScoreEquals(t, got, want)
	})

	t.Run("store wins for existing players", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		store.RecordWin("Chris")

		got := store.GetPlayerScore("Chris")
		want := 34
		assertScoreEquals(t, got, want)
	})

	t.Run("store wins for existing players", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		store.RecordWin("Pepper")

		got := store.GetPlayerScore("Pepper")
		want := 1
		assertScoreEquals(t, got, want)
	})

	t.Run("works with an empty file", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, "")
		defer cleanDatabase()

		_, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)
	})
}

func assertScoreEquals(t testing.TB, got, want int) {
	t.Helper()
	if got != want {
		t.Errorf("got %d want %d", got, want)
	}
}

func assertNoError(t testing.TB, err error) {
	t.Helper()
	if err != nil {
		t.Fatalf("didn't expect an error but got one, %v", err)
	}
}


================================================
FILE: command-line/v1/league.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"io"
)

// League stores a collection of players.
type League []Player

// Find tries to return a player from a league.
func (l League) Find(name string) *Player {
	for i, p := range l {
		if p.Name == name {
			return &l[i]
		}
	}
	return nil
}

// NewLeague creates a league from JSON.
func NewLeague(rdr io.Reader) (League, error) {
	var league []Player
	err := json.NewDecoder(rdr).Decode(&league)

	if err != nil {
		err = fmt.Errorf("problem parsing league, %v", err)
	}

	return league, err
}


================================================
FILE: command-line/v1/server.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
)

// PlayerStore stores score information about players.
type PlayerStore interface {
	GetPlayerScore(name string) int
	RecordWin(name string)
	GetLeague() League
}

// Player stores a name with a number of wins.
type Player struct {
	Name string
	Wins int
}

// PlayerServer is a HTTP interface for player information.
type PlayerServer struct {
	store PlayerStore
	http.Handler
}

const jsonContentType = "application/json"

// NewPlayerServer creates a PlayerServer with routing configured.
func NewPlayerServer(store PlayerStore) *PlayerServer {
	p := new(PlayerServer)

	p.store = store

	router := http.NewServeMux()
	router.Handle("/league", http.HandlerFunc(p.leagueHandler))
	router.Handle("/players/", http.HandlerFunc(p.playersHandler))

	p.Handler = router

	return p
}

func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("content-type", jsonContentType)
	json.NewEncoder(w).Encode(p.store.GetLeague())
}

func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.Request) {
	player := strings.TrimPrefix(r.URL.Path, "/players/")

	switch r.Method {
	case http.MethodPost:
		p.processWin(w, player)
	case http.MethodGet:
		p.showScore(w, player)
	}
}

func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
	score := p.store.GetPlayerScore(player)

	if score == 0 {
		w.WriteHeader(http.StatusNotFound)
	}

	fmt.Fprint(w, score)
}

func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
	p.store.RecordWin(player)
	w.WriteHeader(http.StatusAccepted)
}


================================================
FILE: command-line/v1/server_integration_test.go
================================================
package poker

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestRecordingWinsAndRetrievingThem(t *testing.T) {
	database, cleanDatabase := createTempFile(t, `[]`)
	defer cleanDatabase()
	store, err := NewFileSystemPlayerStore(database)

	assertNoError(t, err)

	server := NewPlayerServer(store)
	player := "Pepper"

	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))

	t.Run("get score", func(t *testing.T) {
		response := httptest.NewRecorder()
		server.ServeHTTP(response, newGetScoreRequest(player))
		assertStatus(t, response.Code, http.StatusOK)

		assertResponseBody(t, response.Body.String(), "3")
	})

	t.Run("get league", func(t *testing.T) {
		response := httptest.NewRecorder()
		server.ServeHTTP(response, newLeagueRequest())
		assertStatus(t, response.Code, http.StatusOK)

		got := getLeagueFromResponse(t, response.Body)
		want := []Player{
			{"Pepper", 3},
		}
		assertLeague(t, got, want)
	})
}


================================================
FILE: command-line/v1/server_test.go
================================================
package poker

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

type StubPlayerStore struct {
	scores   map[string]int
	winCalls []string
	league   []Player
}

func (s *StubPlayerStore) GetPlayerScore(name string) int {
	score := s.scores[name]
	return score
}

func (s *StubPlayerStore) RecordWin(name string) {
	s.winCalls = append(s.winCalls, name)
}

func (s *StubPlayerStore) GetLeague() League {
	return s.league
}

func TestGETPlayers(t *testing.T) {
	store := StubPlayerStore{
		map[string]int{
			"Pepper": 20,
			"Floyd":  10,
		},
		nil,
		nil,
	}
	server := NewPlayerServer(&store)

	t.Run("returns Pepper's score", func(t *testing.T) {
		request := newGetScoreRequest("Pepper")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusOK)
		assertResponseBody(t, response.Body.String(), "20")
	})

	t.Run("returns Floyd's score", func(t *testing.T) {
		request := newGetScoreRequest("Floyd")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusOK)
		assertResponseBody(t, response.Body.String(), "10")
	})

	t.Run("returns 404 on missing players", func(t *testing.T) {
		request := newGetScoreRequest("Apollo")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusNotFound)
	})
}

func TestStoreWins(t *testing.T) {
	store := StubPlayerStore{
		map[string]int{},
		nil,
		nil,
	}
	server := NewPlayerServer(&store)

	t.Run("it records wins on POST", func(t *testing.T) {
		player := "Pepper"

		request := newPostWinRequest(player)
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusAccepted)

		if len(store.winCalls) != 1 {
			t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
		}

		if store.winCalls[0] != player {
			t.Errorf("did not store the correct winner got %q want %q", store.winCalls[0], player)
		}
	})
}

func TestLeague(t *testing.T) {

	t.Run("it returns the league table as JSON", func(t *testing.T) {
		wantedLeague := []Player{
			{"Cleo", 32},
			{"Chris", 20},
			{"Tiest", 14},
		}

		store := StubPlayerStore{nil, nil, wantedLeague}
		server := NewPlayerServer(&store)

		request := newLeagueRequest()
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		got := getLeagueFromResponse(t, response.Body)

		assertStatus(t, response.Code, http.StatusOK)
		assertLeague(t, got, wantedLeague)
		assertContentType(t, response, jsonContentType)

	})
}

func assertContentType(t testing.TB, response *httptest.ResponseRecorder, want string) {
	t.Helper()
	if response.Header().Get("content-type") != want {
		t.Errorf("response did not have content-type of %s, got %v", want, response.Result().Header)
	}
}

func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
	t.Helper()
	league, err := NewLeague(body)

	if err != nil {
		t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", body, err)
	}

	return league
}

func assertLeague(t testing.TB, got, want []Player) {
	t.Helper()
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}

func assertStatus(t testing.TB, got, want int) {
	t.Helper()
	if got != want {
		t.Errorf("did not get correct status, got %d, want %d", got, want)
	}
}

func newLeagueRequest() *http.Request {
	req, _ := http.NewRequest(http.MethodGet, "/league", nil)
	return req
}

func newGetScoreRequest(name string) *http.Request {
	req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil)
	return req
}

func newPostWinRequest(name string) *http.Request {
	req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/players/%s", name), nil)
	return req
}

func assertResponseBody(t testing.TB, got, want string) {
	t.Helper()
	if got != want {
		t.Errorf("response body is wrong, got %q want %q", got, want)
	}
}


================================================
FILE: command-line/v1/tape.go
================================================
package poker

import (
	"io"
	"os"
)

type tape struct {
	file *os.File
}

func (t *tape) Write(p []byte) (n int, err error) {
	t.file.Truncate(0)
	t.file.Seek(0, io.SeekStart)
	return t.file.Write(p)
}


================================================
FILE: command-line/v1/tape_test.go
================================================
package poker

import (
	"io"
	"testing"
)

func TestTape_Write(t *testing.T) {
	file, clean := createTempFile(t, "12345")
	defer clean()

	tape := &tape{file}

	tape.Write([]byte("abc"))

	file.Seek(0, io.SeekStart)
	newFileContents, _ := io.ReadAll(file)

	got := string(newFileContents)
	want := "abc"

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}


================================================
FILE: command-line/v2/CLI.go
================================================
package poker

// CLI helps players through a game of poker.
type CLI struct {
	playerStore PlayerStore
}

// PlayPoker starts the game.
func (cli *CLI) PlayPoker() {
	cli.playerStore.RecordWin("Cleo")
}


================================================
FILE: command-line/v2/CLI_test.go
================================================
package poker

import (
	"testing"
)

func TestCLI(t *testing.T) {
	playerStore := &StubPlayerStore{}

	cli := &CLI{playerStore}
	cli.PlayPoker()

	if len(playerStore.winCalls) != 1 {
		t.Fatal("expected a win call but didn't get any")
	}
}


================================================
FILE: command-line/v2/cmd/cli/main.go
================================================
package main

import "fmt"

func main() {
	fmt.Println("Let's play poker")
}


================================================
FILE: command-line/v2/cmd/webserver/main.go
================================================
package main

import (
	"github.com/quii/learn-go-with-tests/command-line/v1"
	"log"
	"net/http"
	"os"
)

const dbFileName = "game.db.json"

func main() {
	db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)

	if err != nil {
		log.Fatalf("problem opening %s %v", dbFileName, err)
	}

	store, err := poker.NewFileSystemPlayerStore(db)

	if err != nil {
		log.Fatalf("problem creating file system player store, %v ", err)
	}

	server := poker.NewPlayerServer(store)

	log.Fatal(http.ListenAndServe(":5000", server))
}


================================================
FILE: command-line/v2/file_system_store.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sort"
)

// FileSystemPlayerStore stores players in the filesystem.
type FileSystemPlayerStore struct {
	database *json.Encoder
	league   League
}

// NewFileSystemPlayerStore creates a FileSystemPlayerStore initialising the store if needed.
func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, error) {

	err := initialisePlayerDBFile(file)

	if err != nil {
		return nil, fmt.Errorf("problem initialising player db file, %v", err)
	}

	league, err := NewLeague(file)

	if err != nil {
		return nil, fmt.Errorf("problem loading player store from file %s, %v", file.Name(), err)
	}

	return &FileSystemPlayerStore{
		database: json.NewEncoder(&tape{file}),
		league:   league,
	}, nil
}

func initialisePlayerDBFile(file *os.File) error {
	file.Seek(0, io.SeekStart)

	info, err := file.Stat()

	if err != nil {
		return fmt.Errorf("problem getting file info from file %s, %v", file.Name(), err)
	}

	if info.Size() == 0 {
		file.Write([]byte("[]"))
		file.Seek(0, io.SeekStart)
	}

	return nil
}

// GetLeague returns the scores of all the players.
func (f *FileSystemPlayerStore) GetLeague() League {
	sort.Slice(f.league, func(i, j int) bool {
		return f.league[i].Wins > f.league[j].Wins
	})
	return f.league
}

// GetPlayerScore retrieves a player's score.
func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {

	player := f.league.Find(name)

	if player != nil {
		return player.Wins
	}

	return 0
}

// RecordWin will store a win for a player, incrementing wins if already known.
func (f *FileSystemPlayerStore) RecordWin(name string) {
	player := f.league.Find(name)

	if player != nil {
		player.Wins++
	} else {
		f.league = append(f.league, Player{name, 1})
	}

	f.database.Encode(f.league)
}


================================================
FILE: command-line/v2/file_system_store_test.go
================================================
package poker

import (
	"os"
	"testing"
)

func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
	t.Helper()

	tmpfile, err := os.CreateTemp("", "db")

	if err != nil {
		t.Fatalf("could not create temp file %v", err)
	}

	tmpfile.Write([]byte(initialData))

	removeFile := func() {
		os.Remove(tmpfile.Name())
	}

	return tmpfile, removeFile
}

func TestFileSystemStore(t *testing.T) {

	t.Run("league sorted", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		got := store.GetLeague()

		want := []Player{
			{"Chris", 33},
			{"Cleo", 10},
		}

		assertLeague(t, got, want)

		// read again
		got = store.GetLeague()
		assertLeague(t, got, want)
	})

	t.Run("get player score", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		got := store.GetPlayerScore("Chris")
		want := 33
		assertScoreEquals(t, got, want)
	})

	t.Run("store wins for existing players", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		store.RecordWin("Chris")

		got := store.GetPlayerScore("Chris")
		want := 34
		assertScoreEquals(t, got, want)
	})

	t.Run("store wins for existing players", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		store.RecordWin("Pepper")

		got := store.GetPlayerScore("Pepper")
		want := 1
		assertScoreEquals(t, got, want)
	})

	t.Run("works with an empty file", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, "")
		defer cleanDatabase()

		_, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)
	})
}

func assertScoreEquals(t testing.TB, got, want int) {
	t.Helper()
	if got != want {
		t.Errorf("got %d want %d", got, want)
	}
}

func assertNoError(t testing.TB, err error) {
	t.Helper()
	if err != nil {
		t.Fatalf("didn't expect an error but got one, %v", err)
	}
}


================================================
FILE: command-line/v2/league.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"io"
)

// League stores a collection of players.
type League []Player

// Find tries to return a player from a league.
func (l League) Find(name string) *Player {
	for i, p := range l {
		if p.Name == name {
			return &l[i]
		}
	}
	return nil
}

// NewLeague creates a league from JSON.
func NewLeague(rdr io.Reader) (League, error) {
	var league []Player
	err := json.NewDecoder(rdr).Decode(&league)

	if err != nil {
		err = fmt.Errorf("problem parsing league, %v", err)
	}

	return league, err
}


================================================
FILE: command-line/v2/server.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
)

// PlayerStore stores score information about players.
type PlayerStore interface {
	GetPlayerScore(name string) int
	RecordWin(name string)
	GetLeague() League
}

// Player stores a name with a number of wins.
type Player struct {
	Name string
	Wins int
}

// PlayerServer is a HTTP interface for player information.
type PlayerServer struct {
	store PlayerStore
	http.Handler
}

const jsonContentType = "application/json"

// NewPlayerServer creates a PlayerServer with routing configured.
func NewPlayerServer(store PlayerStore) *PlayerServer {
	p := new(PlayerServer)

	p.store = store

	router := http.NewServeMux()
	router.Handle("/league", http.HandlerFunc(p.leagueHandler))
	router.Handle("/players/", http.HandlerFunc(p.playersHandler))

	p.Handler = router

	return p
}

func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("content-type", jsonContentType)
	json.NewEncoder(w).Encode(p.store.GetLeague())
}

func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.Request) {
	player := strings.TrimPrefix(r.URL.Path, "/players/")

	switch r.Method {
	case http.MethodPost:
		p.processWin(w, player)
	case http.MethodGet:
		p.showScore(w, player)
	}
}

func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
	score := p.store.GetPlayerScore(player)

	if score == 0 {
		w.WriteHeader(http.StatusNotFound)
	}

	fmt.Fprint(w, score)
}

func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
	p.store.RecordWin(player)
	w.WriteHeader(http.StatusAccepted)
}


================================================
FILE: command-line/v2/server_integration_test.go
================================================
package poker

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestRecordingWinsAndRetrievingThem(t *testing.T) {
	database, cleanDatabase := createTempFile(t, `[]`)
	defer cleanDatabase()
	store, err := NewFileSystemPlayerStore(database)

	assertNoError(t, err)

	server := NewPlayerServer(store)
	player := "Pepper"

	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))

	t.Run("get score", func(t *testing.T) {
		response := httptest.NewRecorder()
		server.ServeHTTP(response, newGetScoreRequest(player))
		assertStatus(t, response.Code, http.StatusOK)

		assertResponseBody(t, response.Body.String(), "3")
	})

	t.Run("get league", func(t *testing.T) {
		response := httptest.NewRecorder()
		server.ServeHTTP(response, newLeagueRequest())
		assertStatus(t, response.Code, http.StatusOK)

		got := getLeagueFromResponse(t, response.Body)
		want := []Player{
			{"Pepper", 3},
		}
		assertLeague(t, got, want)
	})
}


================================================
FILE: command-line/v2/server_test.go
================================================
package poker

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

type StubPlayerStore struct {
	scores   map[string]int
	winCalls []string
	league   []Player
}

func (s *StubPlayerStore) GetPlayerScore(name string) int {
	score := s.scores[name]
	return score
}

func (s *StubPlayerStore) RecordWin(name string) {
	s.winCalls = append(s.winCalls, name)
}

func (s *StubPlayerStore) GetLeague() League {
	return s.league
}

func TestGETPlayers(t *testing.T) {
	store := StubPlayerStore{
		map[string]int{
			"Pepper": 20,
			"Floyd":  10,
		},
		nil,
		nil,
	}
	server := NewPlayerServer(&store)

	t.Run("returns Pepper's score", func(t *testing.T) {
		request := newGetScoreRequest("Pepper")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusOK)
		assertResponseBody(t, response.Body.String(), "20")
	})

	t.Run("returns Floyd's score", func(t *testing.T) {
		request := newGetScoreRequest("Floyd")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusOK)
		assertResponseBody(t, response.Body.String(), "10")
	})

	t.Run("returns 404 on missing players", func(t *testing.T) {
		request := newGetScoreRequest("Apollo")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusNotFound)
	})
}

func TestStoreWins(t *testing.T) {
	store := StubPlayerStore{
		map[string]int{},
		nil,
		nil,
	}
	server := NewPlayerServer(&store)

	t.Run("it records wins on POST", func(t *testing.T) {
		player := "Pepper"

		request := newPostWinRequest(player)
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusAccepted)

		if len(store.winCalls) != 1 {
			t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
		}

		if store.winCalls[0] != player {
			t.Errorf("did not store the correct winner got %q want %q", store.winCalls[0], player)
		}
	})
}

func TestLeague(t *testing.T) {

	t.Run("it returns the league table as JSON", func(t *testing.T) {
		wantedLeague := []Player{
			{"Cleo", 32},
			{"Chris", 20},
			{"Tiest", 14},
		}

		store := StubPlayerStore{nil, nil, wantedLeague}
		server := NewPlayerServer(&store)

		request := newLeagueRequest()
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		got := getLeagueFromResponse(t, response.Body)

		assertStatus(t, response.Code, http.StatusOK)
		assertLeague(t, got, wantedLeague)
		assertContentType(t, response, jsonContentType)

	})
}

func assertContentType(t testing.TB, response *httptest.ResponseRecorder, want string) {
	t.Helper()
	if response.Header().Get("content-type") != want {
		t.Errorf("response did not have content-type of %s, got %v", want, response.Result().Header)
	}
}

func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
	t.Helper()
	league, err := NewLeague(body)

	if err != nil {
		t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", body, err)
	}

	return league
}

func assertLeague(t testing.TB, got, want []Player) {
	t.Helper()
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}

func assertStatus(t testing.TB, got, want int) {
	t.Helper()
	if got != want {
		t.Errorf("did not get correct status, got %d, want %d", got, want)
	}
}

func newLeagueRequest() *http.Request {
	req, _ := http.NewRequest(http.MethodGet, "/league", nil)
	return req
}

func newGetScoreRequest(name string) *http.Request {
	req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil)
	return req
}

func newPostWinRequest(name string) *http.Request {
	req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/players/%s", name), nil)
	return req
}

func assertResponseBody(t testing.TB, got, want string) {
	t.Helper()
	if got != want {
		t.Errorf("response body is wrong, got %q want %q", got, want)
	}
}


================================================
FILE: command-line/v2/tape.go
================================================
package poker

import (
	"io"
	"os"
)

type tape struct {
	file *os.File
}

func (t *tape) Write(p []byte) (n int, err error) {
	t.file.Truncate(0)
	t.file.Seek(0, io.SeekStart)
	return t.file.Write(p)
}


================================================
FILE: command-line/v2/tape_test.go
================================================
package poker

import (
	"io"
	"testing"
)

func TestTape_Write(t *testing.T) {
	file, clean := createTempFile(t, "12345")
	defer clean()

	tape := &tape{file}

	tape.Write([]byte("abc"))

	file.Seek(0, io.SeekStart)
	newFileContents, _ := io.ReadAll(file)

	got := string(newFileContents)
	want := "abc"

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}


================================================
FILE: command-line/v3/CLI.go
================================================
package poker

import (
	"bufio"
	"io"
	"strings"
)

// CLI helps players through a game of poker.
type CLI struct {
	playerStore PlayerStore
	in          *bufio.Scanner
}

// NewCLI creates a CLI for playing poker.
func NewCLI(store PlayerStore, in io.Reader) *CLI {
	return &CLI{
		playerStore: store,
		in:          bufio.NewScanner(in),
	}
}

// PlayPoker starts the game.
func (cli *CLI) PlayPoker() {
	userInput := cli.readLine()
	cli.playerStore.RecordWin(extractWinner(userInput))
}

func extractWinner(userInput string) string {
	return strings.Replace(userInput, " wins", "", 1)
}

func (cli *CLI) readLine() string {
	cli.in.Scan()
	return cli.in.Text()
}


================================================
FILE: command-line/v3/CLI_test.go
================================================
package poker_test

import (
	"github.com/quii/learn-go-with-tests/command-line/v3"
	"io"
	"strings"
	"testing"
)

func TestCLI(t *testing.T) {

	t.Run("record chris win from user input", func(t *testing.T) {
		in := strings.NewReader("Chris wins\n")
		playerStore := &poker.StubPlayerStore{}

		cli := poker.NewCLI(playerStore, in)
		cli.PlayPoker()

		poker.AssertPlayerWin(t, playerStore, "Chris")
	})

	t.Run("record cleo win from user input", func(t *testing.T) {
		in := strings.NewReader("Cleo wins\n")
		playerStore := &poker.StubPlayerStore{}

		cli := poker.NewCLI(playerStore, in)
		cli.PlayPoker()

		poker.AssertPlayerWin(t, playerStore, "Cleo")
	})

	t.Run("do not read beyond the first newline", func(t *testing.T) {
		in := failOnEndReader{
			t,
			strings.NewReader("Chris wins\n hello there"),
		}

		playerStore := &poker.StubPlayerStore{}

		cli := poker.NewCLI(playerStore, in)
		cli.PlayPoker()
	})

}

type failOnEndReader struct {
	t   *testing.T
	rdr io.Reader
}

func (m failOnEndReader) Read(p []byte) (n int, err error) {

	n, err = m.rdr.Read(p)

	if n == 0 || err == io.EOF {
		m.t.Fatal("Read to the end when you shouldn't have")
	}

	return n, err
}


================================================
FILE: command-line/v3/cmd/cli/main.go
================================================
package main

import (
	"fmt"
	"log"
	"os"

	poker "github.com/quii/learn-go-with-tests/command-line/v3"
)

const dbFileName = "game.db.json"

func main() {
	store, close, err := poker.FileSystemPlayerStoreFromFile(dbFileName)

	if err != nil {
		log.Fatal(err)
	}
	defer close()

	fmt.Println("Let's play poker")
	fmt.Println("Type {Name} wins to record a win")
	poker.NewCLI(store, os.Stdin).PlayPoker()
}


================================================
FILE: command-line/v3/cmd/webserver/main.go
================================================
package main

import (
	"log"
	"net/http"

	poker "github.com/quii/learn-go-with-tests/command-line/v3"
)

const dbFileName = "game.db.json"

func main() {
	store, close, err := poker.FileSystemPlayerStoreFromFile(dbFileName)

	if err != nil {
		log.Fatal(err)
	}
	defer close()

	server := poker.NewPlayerServer(store)

	log.Fatal(http.ListenAndServe(":5000", server))
}


================================================
FILE: command-line/v3/file_system_store.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sort"
)

// FileSystemPlayerStore stores players in the filesystem.
type FileSystemPlayerStore struct {
	database *json.Encoder
	league   League
}

// NewFileSystemPlayerStore creates a FileSystemPlayerStore initialising the store if needed.
func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, error) {

	err := initialisePlayerDBFile(file)

	if err != nil {
		return nil, fmt.Errorf("problem initialising player db file, %v", err)
	}

	league, err := NewLeague(file)

	if err != nil {
		return nil, fmt.Errorf("problem loading player store from file %s, %v", file.Name(), err)
	}

	return &FileSystemPlayerStore{
		database: json.NewEncoder(&tape{file}),
		league:   league,
	}, nil
}

// FileSystemPlayerStoreFromFile creates a PlayerStore from the contents of a JSON file found at path.
func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore, func(), error) {
	db, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)

	if err != nil {
		return nil, nil, fmt.Errorf("problem opening %s, %v", path, err)
	}

	closeFunc := func() {
		db.Close()
	}

	store, err := NewFileSystemPlayerStore(db)

	if err != nil {
		db.Close()
		return nil, nil, fmt.Errorf("problem creating file system player store, %v ", err)
	}

	return store, closeFunc, nil
}

func initialisePlayerDBFile(file *os.File) error {
	file.Seek(0, io.SeekStart)

	info, err := file.Stat()

	if err != nil {
		return fmt.Errorf("problem getting file info from file %s, %v", file.Name(), err)
	}

	if info.Size() == 0 {
		file.Write([]byte("[]"))
		file.Seek(0, io.SeekStart)
	}

	return nil
}

// GetLeague returns the scores of all the players.
func (f *FileSystemPlayerStore) GetLeague() League {
	sort.Slice(f.league, func(i, j int) bool {
		return f.league[i].Wins > f.league[j].Wins
	})
	return f.league
}

// GetPlayerScore retrieves a player's score.
func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {

	player := f.league.Find(name)

	if player != nil {
		return player.Wins
	}

	return 0
}

// RecordWin will store a win for a player, incrementing wins if already known.
func (f *FileSystemPlayerStore) RecordWin(name string) {
	player := f.league.Find(name)

	if player != nil {
		player.Wins++
	} else {
		f.league = append(f.league, Player{name, 1})
	}

	f.database.Encode(f.league)
}


================================================
FILE: command-line/v3/file_system_store_test.go
================================================
package poker

import (
	"os"
	"testing"
)

func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
	t.Helper()

	tmpfile, err := os.CreateTemp("", "db")

	if err != nil {
		t.Fatalf("could not create temp file %v", err)
	}

	tmpfile.Write([]byte(initialData))

	removeFile := func() {
		os.Remove(tmpfile.Name())
	}

	return tmpfile, removeFile
}

func TestFileSystemStore(t *testing.T) {

	t.Run("league sorted", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		got := store.GetLeague()

		want := []Player{
			{"Chris", 33},
			{"Cleo", 10},
		}

		assertLeague(t, got, want)

		// read again
		got = store.GetLeague()
		assertLeague(t, got, want)
	})

	t.Run("get player score", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		got := store.GetPlayerScore("Chris")
		want := 33
		assertScoreEquals(t, got, want)
	})

	t.Run("store wins for existing players", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		store.RecordWin("Chris")

		got := store.GetPlayerScore("Chris")
		want := 34
		assertScoreEquals(t, got, want)
	})

	t.Run("store wins for existing players", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, `[
			{"Name": "Cleo", "Wins": 10},
			{"Name": "Chris", "Wins": 33}]`)
		defer cleanDatabase()

		store, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)

		store.RecordWin("Pepper")

		got := store.GetPlayerScore("Pepper")
		want := 1
		assertScoreEquals(t, got, want)
	})

	t.Run("works with an empty file", func(t *testing.T) {
		database, cleanDatabase := createTempFile(t, "")
		defer cleanDatabase()

		_, err := NewFileSystemPlayerStore(database)

		assertNoError(t, err)
	})
}

func assertScoreEquals(t testing.TB, got, want int) {
	t.Helper()
	if got != want {
		t.Errorf("got %d want %d", got, want)
	}
}

func assertNoError(t testing.TB, err error) {
	t.Helper()
	if err != nil {
		t.Fatalf("didn't expect an error but got one, %v", err)
	}
}


================================================
FILE: command-line/v3/league.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"io"
)

// League stores a collection of players.
type League []Player

// Find tries to return a player from a league.
func (l League) Find(name string) *Player {
	for i, p := range l {
		if p.Name == name {
			return &l[i]
		}
	}
	return nil
}

// NewLeague creates a league from JSON.
func NewLeague(rdr io.Reader) (League, error) {
	var league []Player
	err := json.NewDecoder(rdr).Decode(&league)

	if err != nil {
		err = fmt.Errorf("problem parsing league, %v", err)
	}

	return league, err
}


================================================
FILE: command-line/v3/server.go
================================================
package poker

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
)

// PlayerStore stores score information about players.
type PlayerStore interface {
	GetPlayerScore(name string) int
	RecordWin(name string)
	GetLeague() League
}

// Player stores a name with a number of wins.
type Player struct {
	Name string
	Wins int
}

// PlayerServer is a HTTP interface for player information.
type PlayerServer struct {
	store PlayerStore
	http.Handler
}

const jsonContentType = "application/json"

// NewPlayerServer creates a PlayerServer with routing configured.
func NewPlayerServer(store PlayerStore) *PlayerServer {
	p := new(PlayerServer)

	p.store = store

	router := http.NewServeMux()
	router.Handle("/league", http.HandlerFunc(p.leagueHandler))
	router.Handle("/players/", http.HandlerFunc(p.playersHandler))

	p.Handler = router

	return p
}

func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("content-type", jsonContentType)
	json.NewEncoder(w).Encode(p.store.GetLeague())
}

func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.Request) {
	player := strings.TrimPrefix(r.URL.Path, "/players/")

	switch r.Method {
	case http.MethodPost:
		p.processWin(w, player)
	case http.MethodGet:
		p.showScore(w, player)
	}
}

func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
	score := p.store.GetPlayerScore(player)

	if score == 0 {
		w.WriteHeader(http.StatusNotFound)
	}

	fmt.Fprint(w, score)
}

func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
	p.store.RecordWin(player)
	w.WriteHeader(http.StatusAccepted)
}


================================================
FILE: command-line/v3/server_integration_test.go
================================================
package poker

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestRecordingWinsAndRetrievingThem(t *testing.T) {
	database, cleanDatabase := createTempFile(t, `[]`)
	defer cleanDatabase()
	store, err := NewFileSystemPlayerStore(database)

	assertNoError(t, err)

	server := NewPlayerServer(store)
	player := "Pepper"

	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
	server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))

	t.Run("get score", func(t *testing.T) {
		response := httptest.NewRecorder()
		server.ServeHTTP(response, newGetScoreRequest(player))
		assertStatus(t, response.Code, http.StatusOK)

		assertResponseBody(t, response.Body.String(), "3")
	})

	t.Run("get league", func(t *testing.T) {
		response := httptest.NewRecorder()
		server.ServeHTTP(response, newLeagueRequest())
		assertStatus(t, response.Code, http.StatusOK)

		got := getLeagueFromResponse(t, response.Body)
		want := []Player{
			{"Pepper", 3},
		}
		assertLeague(t, got, want)
	})
}


================================================
FILE: command-line/v3/server_test.go
================================================
package poker

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

func TestGETPlayers(t *testing.T) {
	store := StubPlayerStore{
		map[string]int{
			"Pepper": 20,
			"Floyd":  10,
		},
		nil,
		nil,
	}
	server := NewPlayerServer(&store)

	t.Run("returns Pepper's score", func(t *testing.T) {
		request := newGetScoreRequest("Pepper")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusOK)
		assertResponseBody(t, response.Body.String(), "20")
	})

	t.Run("returns Floyd's score", func(t *testing.T) {
		request := newGetScoreRequest("Floyd")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusOK)
		assertResponseBody(t, response.Body.String(), "10")
	})

	t.Run("returns 404 on missing players", func(t *testing.T) {
		request := newGetScoreRequest("Apollo")
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusNotFound)
	})
}

func TestStoreWins(t *testing.T) {
	store := StubPlayerStore{
		map[string]int{},
		nil,
		nil,
	}
	server := NewPlayerServer(&store)

	t.Run("it records wins on POST", func(t *testing.T) {
		player := "Pepper"

		request := newPostWinRequest(player)
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		assertStatus(t, response.Code, http.StatusAccepted)
		AssertPlayerWin(t, &store, player)
	})
}

func TestLeague(t *testing.T) {

	t.Run("it returns the league table as JSON", func(t *testing.T) {
		wantedLeague := []Player{
			{"Cleo", 32},
			{"Chris", 20},
			{"Tiest", 14},
		}

		store := StubPlayerStore{nil, nil, wantedLeague}
		server := NewPlayerServer(&store)

		request := newLeagueRequest()
		response := httptest.NewRecorder()

		server.ServeHTTP(response, request)

		got := getLeagueFromResponse(t, response.Body)

		assertStatus(t, response.Code, http.StatusOK)
		assertLeague(t, got, wantedLeague)
		assertContentType(t, response, jsonContentType)

	})
}

func assertContentType(t testing.TB, response *httptest.ResponseRecorder, want string) {
	t.Helper()
	if response.Header().Get("content-type") != want {
		t.Errorf("response did not have content-type of %s, got %v", want, response.Result().Header)
	}
}

func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
	t.Helper()
	league, err := NewLeague(body)

	if err != nil {
		t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", body, err)
	}

	return league
}

func assertLeague(t testing.TB, got, want []Player) {
	t.Helper()
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}

func assertStatus(t testing.TB, got, want int) {
	t.Helper()
	if got != want {
		t.Errorf("did not get correct status, got %d, want %d", got, want)
	}
}

func newLeagueRequest() *http.Request {
	req, _ := http.NewRequest(http.MethodGet, "/league", nil)
	return req
}

func newGetScoreRequest(name string) *http.Request {
	req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil)
	return req
}

func newPostWinRequest(name string) *http.Request {
	req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/players/%s", name), nil)
	return req
}

func assertResponseBody(t testing.TB, got, want string) {
	t.Helper()
	if got != want {
		t.Errorf("response body is wrong, got %q want %q", got, want)
	}
}


================================================
FILE: command-line/v3/tape.go
================================================
package poker

import (
	"io"
	"os"
)

type tape struct {
	file *os.File
}

func (t *tape) Write(p []byte) (n int, err error) {
	t.file.Truncate(0)
	t.file.Seek(0, io.SeekStart)
	return t.file.Write(p)
}


================================================
FILE: command-line/v3/tape_test.go
================================================
package poker

import (
	"io"
	"testing"
)

func TestTape_Write(t *testing.T) {
	file, clean := createTempFile(t, "12345")
	defer clean()

	tape := &tape{file}

	tape.Write([]byte("abc"))

	file.Seek(0, io.SeekStart)
	newFileContents, _ := io.ReadAll(file)

	got := string(newFileContents)
	want := "abc"

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}


================================================
FILE: command-line/v3/testing.go
================================================
package poker

import "testing"

// StubPlayerStore implements PlayerStore for testing purposes.
type StubPlayerStore struct {
	Scores   map[string]int
	WinCalls []string
	League   []Player
}

// GetPlayerScore returns a score from Scores.
func (s *StubPlayerStore) GetPlayerScore(name string) int {
	score := s.Scores[name]
	return score
}

// RecordWin will record a win to WinCalls.
func (s *StubPlayerStore) RecordWin(name string) {
	s.WinCalls = append(s.WinCalls, name)
}

// GetLeague returns League.
func (s *StubPlayerStore) GetLeague() League {
	return s.League
}

// AssertPlayerWin allows you to spy on the store's calls to RecordWin.
func AssertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {
	t.Helper()

	if len(store.WinCalls) != 1 {
		t.Fatalf("got %d calls to RecordWin want %d", len(store.WinCalls), 1)
	}

	if store.WinCalls[0] != winner {
		t.Errorf("did not store the correct winner got %q want %q", store.WinCalls[0], winner)
	}
}


================================================
FILE: command-line.md
================================================
# Command line and project structure

**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main/command-line)**

Our product owner now wants to _pivot_ by introducing a second application - a command line application.

For now, it will just need to be able to record a player's win when the user types `Ruth wins`. The intention is to eventually be a tool for helping users play poker.

The product owner wants the database to be shared amongst the two applications so that the league updates according to wins recorded in the new application.

## A reminder of the code

We have an application with a `main.go` file that launches an HTTP server. The HTTP server won't be interesting to us for this exercise but the abstraction it uses will. It depends on a `PlayerStore`.

```go
type PlayerStore interface {
	GetPlayerScore(name string) int
	RecordWin(name string)
	GetLeague() League
}
```

In the previous chapter, we made a `FileSystemPlayerStore` which implements that interface. We should be able to re-use some of this for our new application.

## Some project refactoring first

Our project now needs to create two binaries, our existing web server and the command line app.

Before we get stuck into our new work we should structure our project to accommodate this.

So far all the code has lived in one folder, in a path looking like this

`$GOPATH/src/github.com/your-name/my-app`

In order for you to make an application in Go, you need a `main` function inside a `package main`. So far all of our "domain" code has lived inside `package main` and our `func main` can reference everything.

This was fine so far and it is good practice not to go over-the-top with package structure. If you take the time to look through the standard library you will see very little in the way of lots of folders and structure.

Thankfully it's pretty straightforward to add structure _when you need it_.

Inside the existing project create a `cmd` directory with a `webserver` directory inside that (e.g `mkdir -p cmd/webserver`).

Move the `main.go` inside there.

If you have `tree` installed you should run it and your structure should look like this

```
.
|-- file_system_store.go
|-- file_system_store_test.go
|-- cmd
|   |-- webserver
|       |-- main.go
|-- league.go
|-- server.go
|-- server_integration_test.go
|-- server_test.go
|-- tape.go
|-- tape_test.go
```

We now effectively have a separation between our application and the library code but we now need to change some package names. Remember when you build a Go application its package _must_ be `main`.

Change all the other code to have a package called `poker`.

Finally, we need to import this package into `main.go` so we can use it to create our web server. Then we can use our library code by using `poker.FunctionName`.

The paths will be different on your computer, but it should be similar to this:

```go
// cmd/webserver/main.go
package main

import (
	"github.com/quii/learn-go-with-tests/command-line/v1"
	"log"
	"net/http"
	"os"
)

const dbFileName = "game.db.json"

func main() {
	db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)

	if err != nil {
		log.Fatalf("problem opening %s %v", dbFileName, err)
	}

	store, err := poker.NewFileSystemPlayerStore(db)

	if err != nil {
		log.Fatalf("problem creating file system player store, %v ", err)
	}

	server := poker.NewPlayerServer(store)

	log.Fatal(http.ListenAndServe(":5000", server))
}
```

The full path may seem a bit jarring, but this is how you can import _any_ publicly available library into your code.

By separating our domain code into a separate package and committing it to a public repo like GitHub any Go developer can write their own code which imports that package the features we've written available. The first time you try and run it will complain it is not existing but all you need to do is run `go get`.

In addition, users can view [the documentation at pkg.go.dev](https://pkg.go.dev/github.com/quii/learn-go-with-tests/command-line/v1).

### Final checks

- Inside the root run `go test` and check they're still passing
- Go inside our `cmd/webserver` and do `go run main.go`
  - Visit `http://localhost:5000/league` and you should see it's still working

### Walking skeleton

Before we get stuck into writing tests, let's add a new application that our project will build. Create another directory inside `cmd` called `cli` (command line interface) and add a `main.go` with the following

```go
// cmd/cli/main.go
package main

import "fmt"

func main() {
	fmt.Println("Let's play poker")
}
```

The first requirement we'll tackle is recording a win when the user types `{PlayerName} wins`.

## Write the test first

We know we need to make something called `CLI` which will allow us to `Play` poker. It'll need to read user input and then record wins to a `PlayerStore`.

Before we jump too far ahead though, let's just write a test to check it integrates with the `PlayerStore` how we'd like.

Inside `CLI_test.go` (in the root of the project, not inside `cmd`)

```go
// CLI_test.go
package poker

import "testing"

func TestCLI(t *testing.T) {
	playerStore := &StubPlayerStore{}
	cli := &CLI{playerStore}
	cli.PlayPoker()

	if len(playerStore.winCalls) != 1 {
		t.Fatal("expected a win call but didn't get any")
	}
}
```

- We can use our `StubPlayerStore` from other tests
- We pass in our dependency into our not yet existing `CLI` type
- Trigger the game by an unwritten `PlayPoker` method
- Check that a win is recorded

## Try to run the test

```
# github.com/quii/learn-go-with-tests/command-line/v2
./cli_test.go:25:10: undefined: CLI
```

## Write the minimal amount of code for the test to run and check the failing test output

At this point, you should be comfortable enough to create our new `CLI` struct with the respective field for our dependency and add a method.

You should end up with code like this

```go
// CLI.go
package poker

type CLI struct {
	playerStore PlayerStore
}

func (cli *CLI) PlayPoker() {}
```

Remember we're just trying to get the test running so we can check the test fails how we'd hope

```
--- FAIL: TestCLI (0.00s)
    cli_test.go:30: expected a win call but didn't get any
FAIL
```

## Write enough code to make it pass

```go
//CLI.go
func (cli *CLI) PlayPoker() {
	cli.playerStore.RecordWin("Cleo")
}
```

That should make it pass.

Next, we need to simulate reading from `Stdin` (the input from the user) so that we can record wins for specific players.

Let's extend our test to exercise this.

## Write the test first

```go
//CLI_test.go
func TestCLI(t *testing.T) {
	in := strings.NewReader("Chris wins\n")
	playerStore := &StubPlayerStore{}

	cli := &CLI{playerStore, in}
	cli.PlayPoker()

	if len(playerStore.winCalls) != 1 {
		t.Fatal("expected a win call but didn't get any")
	}

	got := playerStore.winCalls[0]
	want := "Chris"

	if got != want {
		t.Errorf("didn't record correct winner, got %q, want %q", got, want)
	}
}
```

`os.Stdin` is what we'll use in `main` to capture the user's input. It is a `*File` under the hood which means it implements `io.Reader` which as we know by now is a handy way of capturing text.

We create an `io.Reader` in our test using the handy `strings.NewReader`, filling it with what we expect the user to type.

## Try to run the test

`./CLI_test.go:12:32: too many values in struct initializer`

## Write the minimal amount of code for the test to run and check the failing test output

We need to add our new dependency into `CLI`.

```go
//CLI.go
type CLI struct {
	playerStore PlayerStore
	in          io.Reader
}
```

```
--- FAIL: TestCLI (0.00s)
    CLI_test.go:23: didn't record the correct winner, got 'Cleo', want 'Chris'
FAIL
```

## Write enough code to make it pass

Remember to do the strictly easiest thing first

```go
func (cli *CLI) PlayPoker() {
	cli.playerStore.RecordWin("Chris")
}
```

The test passes. We'll add another test to force us to write some real code next, but first, let's refactor.

## Refactor

In `server_test` we earlier did checks to see if wins are recorded as we have here. Let's DRY that assertion up into a helper

```go
//server_test.go
func assertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {
	t.Helper()

	if len(store.winCalls) != 1 {
		t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
	}

	if store.winCalls[0] != winner {
		t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], winner)
	}
}
```

Now replace the assertions in both `server_test.go` and `CLI_test.go`.

The test should now read like so

```go
//CLI_test.go
func TestCLI(t *testing.T) {
	in := strings.NewReader("Chris wins\n")
	playerStore := &StubPlayerStore{}

	cli := &CLI{playerStore, in}
	cli.PlayPoker()

	assertPlayerWin(t, playerStore, "Chris")
}
```

Now let's write _another_ test with different user input to force us into actually reading it.

## Write the test first

```go
//CLI_test.go
func TestCLI(t *testing.T) {

	t.Run("record chris win from user input", func(t *testing.T) {
		in := strings.NewReader("Chris wins\n")
		playerStore := &StubPlayerStore{}

		cli := &CLI{playerStore, in}
		cli.PlayPoker()

		assertPlayerWin(t, playerStore, "Chris")
	})

	t.Run("record cleo win from user input", func(t *testing.T) {
		in := strings.NewReader("Cleo wins\n")
		playerStore := &StubPlayerStore{}

		cli := &CLI{playerStore, in}
		cli.PlayPoker()

		assertPlayerWin(t, playerStore, "Cleo")
	})

}
```

## Try to run the test

```
=== RUN   TestCLI
--- FAIL: TestCLI (0.00s)
=== RUN   TestCLI/record_chris_win_from_user_input
    --- PASS: TestCLI/record_chris_win_from_user_input (0.00s)
=== RUN   TestCLI/record_cleo_win_from_user_input
    --- FAIL: TestCLI/record_cleo_win_from_user_input (0.00s)
        CLI_test.go:27: did not store correct winner got 'Chris' want 'Cleo'
FAIL
```

## Write enough code to make it pass

We'll use a [`bufio.Scanner`](https://golang.org/pkg/bufio/) to read the input from the `io.Reader`.

> Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer object, creating another object (Reader or Writer) that also implements the interface but provides buffering and some help for textual I/O.

Update the code to the following

```go
//CLI.go
type CLI struct {
	playerStore PlayerStore
	in          io.Reader
}

func (cli *CLI) PlayPoker() {
	reader := bufio.NewScanner(cli.in)
	reader.Scan()
	cli.playerStore.RecordWin(extractWinner(reader.Text()))
}

func extractWinner(userInput string) string {
	return strings.Replace(userInput, " wins", "", 1)
}
```

The tests will now pass.

- `Scanner.Scan()` will read up to a newline.
- We then use `Scanner.Text()` to return the `string` the scanner read to.

Now that we have some passing tests, we should wire this up into `main`. Remember we should always strive to have fully-integrated working software as quickly as we can.

In `main.go` add the following and run it. (you may have to adjust the path of the second dependency to match what's on your computer)

```go
package main

import (
	"fmt"
	"github.com/quii/learn-go-with-tests/command-line/v3"
	"log"
	"os"
)

const dbFileName = "game.db.json"

func main() {
	fmt.Println("Let's play poker")
	fmt.Println("Type {Name} wins to record a win")

	db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)

	if err != nil {
		log.Fatalf("problem opening %s %v", dbFileName, err)
	}

	store, err := poker.NewFileSystemPlayerStore(db)

	if err != nil {
		log.Fatalf("problem creating file system player store, %v ", err)
	}

	game := poker.CLI{store, os.Stdin}
	game.PlayPoker()
}
```

You should get an error

```
command-line/v3/cmd/cli/main.go:32:25: implicit assignment of unexported field 'playerStore' in poker.CLI literal
command-line/v3/cmd/cli/main.go:32:34: implicit assignment of unexported field 'in' in poker.CLI literal
```

What's happening here is because we are trying to assign to the fields `playerStore` and `in` in `CLI`. These are unexported (private) fields. We _could_ do this in our test code because our test is in the same package as `CLI` (`poker`). But our `main` is in package `main` so it does not have access.

This highlights the importance of _integrating your work_. We rightfully made the dependencies of our `CLI` private (because we don't want them exposed to users of `CLI`s) but haven't made a way for users to construct it.

Is there a way to have caught this problem earlier?

### `package mypackage_test`

In all other examples so far, when we make a test file we declare it as being in the same package that we are testing.

This is fine and it means on the odd occasion where we want to test something internal to the package we have access to the unexported types.

But given we have advocated for _not_ testing internal things _generally_, can Go help enforce that? What if we could test our code where we only have access to the exported types (like our `main` does)?

When you're writing a project with multiple packages I would strongly recommend that your test package name has `_test` at the end. When you do this you will only be able to have access to the public types in your package. This would help with this specific case but also helps enforce the discipline of only testing public APIs. If you still wish to test internals you can make a separate test with the package you want to test.

An adage with TDD is that if you cannot test your code then it is probably hard for users of your code to integrate with it. Using `package foo_test` will help with this by forcing you to test your code as if you are importing it like users of your package will.

Before fixing `main` let's change the package of our test inside `CLI_test.go` to `poker_test`.

If you have a well-configured IDE you will suddenly see a lot of red! If you run the compiler you'll get the following errors

```
./CLI_test.go:12:19: undefined: StubPlayerStore
./CLI_test.go:17:3: undefined: assertPlayerWin
./CLI_test.go:22:19: undefined: StubPlayerStore
./CLI_test.go:27:3: undefined: assertPlayerWin
```

We have now stumbled into more questions on package design. In order to test our software we made unexported stubs and helper functions which are no longer available for us to use in our `CLI_test` because the helpers are defined in the `_test.go` files in the `poker` package.

#### Do we want to have our stubs and helpers 'public'?

This is a subjective discussion. One could argue that you do not want to pollute your package's API with code to facilitate tests.

In the presentation ["Advanced Testing with Go"](https://speakerdeck.com/mitchellh/advanced-testing-with-go?slide=53) by Mitchell Hashimoto, it is described how at HashiCorp they advocate doing this so that users of the package can write tests without having to re-invent the wheel writing stubs. In our case, this would mean anyone using our `poker` package won't have to create their own stub `PlayerStore` if they wish to work with our code.

Anecdotally I have used this technique in other shared packages and it has proved extremely useful in terms of users saving time when integrating with our packages.

So let's create a file called `testing.go` and add our stub and our helpers.

```go
// testing.go
package poker

import "testing"

type StubPlayerStore struct {
	scores   map[string]int
	winCalls []string
	league   []Player
}

func (s *StubPlayerStore) GetPlayerScore(name string) int {
	score := s.scores[name]
	return score
}

func (s *StubPlayerStore) RecordWin(name string) {
	s.winCalls = append(s.winCalls, name)
}

func (s *StubPlayerStore) GetLeague() League {
	return s.league
}

func AssertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {
	t.Helper()

	if len(store.winCalls) != 1 {
		t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
	}

	if store.winCalls[0] != winner {
		t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], winner)
	}
}

// todo for you - the rest of the helpers
```

You'll need to make the helpers public (remember exporting is done with a capital letter at the start) if you want them to be exposed to importers of our package.

In our `CLI` test you'll need to call the code as if you were using it within a different package.

```go
//CLI_test.go
func TestCLI(t *testing.T) {

	t.Run("record chris win from user input", func(t *testing.T) {
		in := strings.NewReader("Chris wins\n")
		playerStore := &poker.StubPlayerStore{}

		cli := &poker.CLI{playerStore, in}
		cli.PlayPoker()

		poker.AssertPlayerWin(t, playerStore, "Chris")
	})

	t.Run("record cleo win from user input", func(t *testing.T) {
		in := strings.NewReader("Cleo wins\n")
		playerStore := &poker.StubPlayerStore{}

		cli := &poker.CLI{playerStore, in}
		cli.PlayPoker()

		poker.AssertPlayerWin(t, playerStore, "Cleo")
	})

}
```

You'll now see we have the same problems as we had in `main`

```
./CLI_test.go:15:26: implicit assignment of unexported field 'playerStore' in poker.CLI literal
./CLI_test.go:15:39: implicit assignment of unexported field 'in' in poker.CLI literal
./CLI_test.go:25:26: implicit assignment of unexported field 'playerStore' in poker.CLI literal
./CLI_test.go:25:39: implicit assignment of unexported field 'in' in poker.CLI literal
```

The easiest way to get around this is to make a constructor as we have for other types. We'll also change `CLI` so it stores a `bufio.Scanner` instead of the reader as it's now automatically wrapped at construction time.

```go
//CLI.go
type CLI struct {
	playerStore PlayerStore
	in          *bufio.Scanner
}

func NewCLI(store PlayerStore, in io.Reader) *CLI {
	return &CLI{
		playerStore: store,
		in:          bufio.NewScanner(in),
	}
}
```

By doing this, we can then simplify and refactor our reading code

```go
//CLI.go
func (cli *CLI) PlayPoker() {
	userInput := cli.readLine()
	cli.playerStore.RecordWin(extractWinner(userInput))
}

func extractWinner(userInput string) string {
	return strings.Replace(userInput, " wins", "", 1)
}

func (cli *CLI) readLine() string {
	cli.in.Scan()
	return cli.in.Text()
}
```

Change the test to use the constructor instead and we should be back to the tests passing.

Finally, we can go back to our new `main.go` and use the constructor we just made

```go
//cmd/cli/main.go
game := poker.NewCLI(store, os.Stdin)
```

Try and run it, type "Bob wins".

### Refactor

We have some repetition in our respective applications where we are opening a file and creating a `file_system_store` from its contents. This feels like a slight weakness in our package's design so we should make a function in it to encapsulate opening a file from a path and returning you the `PlayerStore`.

```go
//file_system_store.go
func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore, func(), error) {
	db, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)

	if err != nil {
		return nil, nil, fmt.Errorf("problem opening %s %v", path, err)
	}

	closeFunc := func() {
		db.Close()
	}

	store, err := NewFileSystemPlayerStore(db)

	if err != nil {
		return nil, nil, fmt.Errorf("problem creating file system player store, %v ", err)
	}

	return store, closeFunc, nil
}
```

Now refactor both of our applications to use this function to create the store.

#### CLI application code

```go
// cmd/cli/main.go
package main

import (
	"fmt"
	"github.com/quii/learn-go-with-tests/command-line/v3"
	"log"
	"os"
)

const dbFileName = "game.db.json"

func main() {
	store, close, err := poker.FileSystemPlayerStoreFromFile(dbFileName)

	if err != nil {
		log.Fatal(err)
	}
	defer close()

	fmt.Println("Let's play poker")
	fmt.Println("Type {Name} wins to record a win")
	poker.NewCLI(store, os.Stdin).PlayPoker()
}
```

#### Web server application code

```go
// cmd/webserver/main.go
package main

import (
	"github.com/quii/learn-go-with-tests/command-line/v3"
	"log"
	"net/http"
)

const dbFileName = "game.db.json"

func main() {
	store, close, err := poker.FileSystemPlayerStoreFromFile(dbFileName)

	if err != nil {
		log.Fatal(err)
	}
	defer close()

	server := poker.NewPlayerServer(store)

	if err := http.ListenAndServe(":5000", server); err != nil {
		log.Fatalf("could not listen on port 5000 %v", err)
	}
}
```

Notice the symmetry: despite being different user interfaces the setup is almost identical. This feels like good validation of our design so far.
And notice also that `FileSystemPlayerStoreFromFile` returns a closing function, so we can close the underlying file once we are done using the Store.

## Wrapping up

### Package structure

This chapter meant we wanted to create two applications, re-using the domain code we've written so far. In order to do this, we needed to update our package structure so that we had separate folders for our respective `main`s.

By doing this we ran into integration problems due to unexported values so this further demonstrates the value of working in small "slices" and integrating often.

We learned how `mypackage_test` helps us create a testing environment which is the same experience for other packages integrating with your code, to help you catch integration problems and see how easy (or not!) your code is to work with.

### Reading user input

We saw how reading from `os.Stdin` is very easy for us to work with as it implements `io.Reader`. We used `bufio.Scanner` to easily read line by line user input.

### Simple abstractions leads to simpler code re-use

It was almost no effort to integrate `PlayerStore` into our new application (once we had made the package adjustments) and subsequently testing was very easy too because we decided to expose our stub version too.


================================================
FILE: concurrency/v1/check_website.go
================================================
package concurrency

import "net/http"

// CheckWebsite returns true if the URL returns a 200 status code, false otherwise.
func CheckWebsite(url string) bool {
	response, err := http.Head(url)
	if err != nil {
		return false
	}

	return response.StatusCode == http.StatusOK
}


================================================
FILE: concurrency/v1/check_websites.go
================================================
package concurrency

// WebsiteChecker checks a url, returning a bool.
type WebsiteChecker func(string) bool

// CheckWebsites takes a WebsiteChecker and a slice of urls and returns  a map.
// of urls to the result of checking each url with the WebsiteChecker function.
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
	results := make(map[string]bool)

	for _, url := range urls {
		results[url] = wc(url)
	}

	return results
}


================================================
FILE: concurrency/v1/check_websites_benchmark_test.go
================================================
package concurrency

import (
	"testing"
	"time"
)

func slowStubWebsiteChecker(_ string) bool {
	time.Sleep(20 * time.Millisecond)
	return true
}

func BenchmarkCheckWebsites(b *testing.B) {
	urls := make([]string, 100)
	for i := 0; i < len(urls); i++ {
		urls[i] = "a url"
	}

	for b.Loop() {
		CheckWebsites(slowStubWebsiteChecker, urls)
	}
}


================================================
FILE: concurrency/v1/check_websites_test.go
================================================
package concurrency

import (
	"reflect"
	"testing"
)

func mockWebsiteChecker(url string) bool {
	if url == "waat://furhurterwe.geds" {
		return false
	}
	return true
}

func TestCheckWebsites(t *testing.T) {
	websites := []string{
		"http://google.com",
		"http://blog.gypsydave5.com",
		"waat://furhurterwe.geds",
	}

	want := map[string]bool{
		"http://google.com":          true,
		"http://blog.gypsydave5.com": true,
		"waat://furhurterwe.geds":    false,
	}

	got := CheckWebsites(mockWebsiteChecker, websites)

	if !reflect.DeepEqual(want, got) {
		t.Fatalf("wanted %v, got %v", want, got)
	}
}


================================================
FILE: concurrency/v2/check_website.go
================================================
package concurrency

import "net/http"

// CheckWebsite returns true if the URL returns a 200 status code, false otherwise.
func CheckWebsite(url string) bool {
	response, err := http.Head(url)
	if err != nil {
		return false
	}

	return response.StatusCode == http.StatusOK
}


================================================
FILE: concurrency/v2/check_websites.go
================================================
package concurrency

import (
	"time"
)

// WebsiteChecker checks a url, returning a bool.
type WebsiteChecker func(string) bool

// CheckWebsites takes a WebsiteChecker and a slice of urls and returns  a map.
// of urls to the result of checking each url with the WebsiteChecker function.
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
	results := make(map[string]bool)

	for _, url := range urls {
		go func() {
			results[url] = wc(url)
		}()
	}

	time.Sleep(2 * time.Second)

	return results
}


================================================
FILE: concurrency/v2/check_websites_benchmark_test.go
================================================
package concurrency

import (
	"testing"
	"time"
)

func slowStubWebsiteChecker(_ string) bool {
	time.Sleep(20 * time.Millisecond)
	return true
}

func BenchmarkCheckWebsites(b *testing.B) {
	urls := make([]string, 100)
	for i := 0; i < len(urls); i++ {
		urls[i] = "a url"
	}

	for b.Loop() {
		CheckWebsites(slowStubWebsiteChecker, urls)
	}
}


================================================
FILE: concurrency/v2/check_websites_test.go
================================================
package concurrency

import (
	"reflect"
	"testing"
)

func mockWebsiteChecker(url string) bool {
	if url == "waat://furhurterwe.geds" {
		return false
	}
	return true
}

func TestCheckWebsites(t *testing.T) {
	websites := []string{
		"http://google.com",
		"http://blog.gypsydave5.com",
		"waat://furhurterwe.geds",
	}

	want := map[string]bool{
		"http://google.com":          true,
		"http://blog.gypsydave5.com": true,
		"waat://furhurterwe.geds":    false,
	}

	got := CheckWebsites(mockWebsiteChecker, websites)

	if !reflect.DeepEqual(want, got) {
		t.Fatalf("wanted %v, got %v", want, got)
	}
}


================================================
FILE: concurrency/v3/check_website.go
================================================
package concurrency

import "net/http"

// CheckWebsite returns true if the URL returns a 200 status code, false otherwise.
func CheckWebsite(url string) bool {
	response, err := http.Head(url)
	if err != nil {
		return false
	}

	return response.StatusCode == http.StatusOK
}


================================================
FILE: concurrency/v3/check_websites.go
================================================
package concurrency

// WebsiteChecker checks a url, returning a bool.
type WebsiteChecker func(string) bool
type result struct {
	string
	bool
}

// CheckWebsites takes a WebsiteChecker and a slice of urls and returns a map.
// of urls to the result of checking each url with the WebsiteChecker function.
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
	results := make(map[string]bool)
	resultChannel := make(chan result)

	for _, url := range urls {
		go func() {
			resultChannel <- result{url, wc(url)}
		}()
	}

	for i := 0; i < len(urls); i++ {
		r := <-resultChannel
		results[r.string] = r.bool
	}

	return results
}


================================================
FILE: concurrency/v3/check_websites_benchmark_test.go
================================================
package concurrency

import (
	"testing"
	"time"
)

func slowStubWebsiteChecker(_ string) bool {
	time.Sleep(20 * time.Millisecond)
	return true
}

func BenchmarkCheckWebsites(b *testing.B) {
	urls := make([]string, 100)
	for i := 0; i < len(urls); i++ {
		urls[i] = "a url"
	}

	for b.Loop() {
		CheckWebsites(slowStubWebsiteChecker, urls)
	}
}


================================================
FILE: concurrency/v3/check_websites_test.go
================================================
package concurrency

import (
	"reflect"
	"testing"
)

func mockWebsiteChecker(url string) bool {
	if url == "waat://furhurterwe.geds" {
		return false
	}
	return true
}

func TestCheckWebsites(t *testing.T) {
	websites := []string{
		"http://google.com",
		"http://blog.gypsydave5.com",
		"waat://furhurterwe.geds",
	}

	want := map[string]bool{
		"http://google.com":          true,
		"http://blog.gypsydave5.com": true,
		"waat://furhurterwe.geds":    false,
	}

	got := CheckWebsites(mockWebsiteChecker, websites)

	if !reflect.DeepEqual(want, got) {
		t.Fatalf("wanted %v, got %v", want, got)
	}
}


================================================
FILE: concurrency.md
================================================
# Concurrency

**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main/concurrency)**

Here's the setup: a colleague has written a function, `CheckWebsites`, that
checks the status of a list of URLs.

```go
package concurrency

type WebsiteChecker func(string) bool

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
	results := make(map[string]bool)

	for _, url := range urls {
		results[url] = wc(url)
	}

	return results
}
```

It returns a map of each URL checked to a boolean value: `true` for a good
response; `false` for a bad response.

You also have to pass in a `WebsiteChecker` which takes a single URL and returns
a boolean. This is used by the function to check all the websites.

Using [dependency injection][DI] has allowed them to test the function without
making real HTTP calls, making it reliable and fast.

Here's the test they've written:

```go
package concurrency

import (
	"reflect"
	"testing"
)

func mockWebsiteChecker(url string) bool {
	return url != "waat://furhurterwe.geds"
}

func TestCheckWebsites(t *testing.T) {
	websites := []string{
		"http://google.com",
		"http://blog.gypsydave5.com",
		"waat://furhurterwe.geds",
	}

	want := map[string]bool{
		"http://google.com":          true,
		"http://blog.gypsydave5.com": true,
		"waat://furhurterwe.geds":    false,
	}

	got := CheckWebsites(mockWebsiteChecker, websites)

	if !reflect.DeepEqual(want, got) {
		t.Fatalf("wanted %v, got %v", want, got)
	}
}
```

The function is in production and being used to check hundreds of websites. But
your colleague has started to get complaints that it's slow, so they've asked
you to help speed it up.

## Write a test

Let's use a benchmark to test the speed of `CheckWebsites` so that we can see the
effect of our changes.

```go
package concurrency

import (
	"testing"
	"time"
)

func slowStubWebsiteChecker(_ string) bool {
	time.Sleep(20 * time.Millisecond)
	return true
}

func BenchmarkCheckWebsites(b *testing.B) {
	urls := make([]string, 100)
	for i := 0; i < len(urls); i++ {
		urls[i] = "a url"
	}

	for b.Loop() {
		CheckWebsites(slowStubWebsiteChecker, urls)
	}
}
```

The benchmark tests `CheckWebsites` using a slice of one hundred urls and uses
a new fake implementation of `WebsiteChecker`. `slowStubWebsiteChecker` is
deliberately slow. It uses `time.Sleep` to wait exactly twenty milliseconds and
then it returns true.


When we run the benchmark using `go test -bench=.` (or if you're in Windows Powershell `go test -bench="."`):

```sh
pkg: github.com/gypsydave5/learn-go-with-tests/concurrency/v0
BenchmarkCheckWebsites-4               1        2249228637 ns/op
PASS
ok      github.com/gypsydave5/learn-go-with-tests/concurrency/v0        2.268s
```

`CheckWebsites` has been benchmarked at 2249228637 nanoseconds - about two and
a quarter seconds.

Let's try and make this faster.

### Write enough code to make it pass

Now we can finally talk about concurrency which, for the purposes of the
following, means "having more than one thing in progress." This is something
that we do naturally everyday.

For instance, this morning I made a cup of tea. I put the kettle on and then,
while I was waiting for it to boil, I got the milk out of the fridge, got the
tea out of the cupboard, found my favourite mug, put the teabag into the cup and
then, when the kettle had boiled, I put the water in the cup.

What I _didn't_ do was put the kettle on and then stand there blankly staring at
the kettle until it boiled, then do everything else once the kettle had boiled.

If you can understand why it's faster to make tea the first way, then you can
understand how we will make `CheckWebsites` faster. Instead of waiting for
a website to respond before sending a request to the next website, we will tell
our computer to make the next request while it is waiting.

Normally in Go when we call a function `doSomething()` we wait for it to return
(even if it has no value to return, we still wait for it to finish). We say that
this operation is *blocking* - it makes us wait for it to finish. An operation
that does not block in Go will run in a separate *process* called a *goroutine*.
Think of a process as reading down the page of Go code from top to bottom, going
'inside' each function when it gets called to read what it does. When a separate
process starts, it's like another reader begins reading inside the function,
leaving the original reader to carry on going down the page.

To tell Go to start a new goroutine we turn a function call into a `go`
statement by putting the keyword `go` in front of it: `go doSomething()`.

```go
package concurrency

type WebsiteChecker func(string) bool

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
	results := make(map[string]bool)

	for _, url := range urls {
		go func() {
			results[url] = wc(url)
		}()
	}

	return results
}
```

Because the only way to start a goroutine is to put `go` in front of a function
call, we often use *anonymous functions* when we want to start a goroutine. An
anonymous function literal looks just the same as a normal function declaration,
but without a name (unsurprisingly). You can see one above in the body of the
`for` loop.

Anonymous functions have a number of features which make them useful, two of
which we're using above. Firstly, they can be executed at the same time that
they're declared - this is what the `()` at the end of the anonymous function is
doing. Secondly they maintain access to the lexical scope in which they are
defined - all the variables that are available at the point when you declare the
anonymous function are also available in the body of the function.

The body of the anonymous function above is just the same as the loop body was
before. The only difference is that each iteration of the loop will start a new
goroutine, concurrent with the current process (the `WebsiteChecker` function).
Each goroutine will add its result to the results map.

But when we run `go test`:

```sh
--- FAIL: TestCheckWebsites (0.00s)
        CheckWebsites_test.go:31: Wanted map[http://google.com:true http://blog.gypsydave5.com:true waat://furhurterwe.geds:false], got map[]
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/concurrency/v1        0.010s

```

### A quick aside into the concurrency universe...

You might not get this result. You might get a panic message that
we're going to talk about in a bit. Don't worry if you got that, just keep
running the test until you _do_ get the result above. Or pretend that you did.
Up to you. Welcome to concurrency: when it's not handled correctly it's hard to
predict what's going to happen. Don't worry - that's why we're writing tests, to
help us know when we're handling concurrency predictably.

### ... and we're back.

We are caught by the original test `CheckWebsites`, it's now returning an
empty map. What went wrong?

None of the goroutines that our `for` loop started had enough time to add
their result to the `results` map; the `CheckWebsites` function is too fast for
them, and it returns the still empty map.

To fix this we can just wait while all the goroutines do their work, and then
return. Two seconds ought to do it, right?

```go
package concurrency

import "time"

type WebsiteChecker func(string) bool

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
	results := make(map[string]bool)

	for _, url := range urls {
		go func() {
			results[url] = wc(url)
		}()
	}

	time.Sleep(2 * time.Second)

	return results
}
```

Now if you're lucky you'll get:

```sh
PASS
ok      github.com/gypsydave5/learn-go-with-tests/concurrency/v1        2.012s
```

But if you're unlucky (this is more likely if you run them with the benchmark as you'll get more tries)

```sh
fatal error: concurrent map writes

goroutine 8 [running]:
runtime.throw(0x12c5895, 0x15)
        /usr/local/Cellar/go/1.9.3/libexec/src/runtime/panic.go:605 +0x95 fp=0xc420037700 sp=0xc4200376e0 pc=0x102d395
runtime.mapassign_faststr(0x1271d80, 0xc42007acf0, 0x12c6634, 0x17, 0x0)
        /usr/local/Cellar/go/1.9.3/libexec/src/runtime/hashmap_fast.go:783 +0x4f5 fp=0xc420037780 sp=0xc420037700 pc=0x100eb65
github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker.func1(0xc42007acf0, 0x12d3938, 0x12c6634, 0x17)
        /Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12 +0x71 fp=0xc4200377c0 sp=0xc420037780 pc=0x12308f1
runtime.goexit()
        /usr/local/Cellar/go/1.9.3/libexec/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc4200377c8 sp=0xc4200377c0 pc=0x105cf01
created by github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker
        /Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11 +0xa1

        ... many more scary lines of text ...
```

This is long and scary, but all we need to do is take a breath and read the
stacktrace: `fatal error: concurrent map writes`. Sometimes, when we run our
tests, two of the goroutines write to the results map at exactly the same time.
Maps in Go don't like it when more than one thing tries to write to them at
once, and so `fatal error`.

This is a _race condition_, a bug that occurs when the output of our software is
dependent on the timing and sequence of events that we have no control over.
Because we cannot control exactly when each goroutine writes to the results map,
we are vulnerable to two goroutines writing to it at the same time.

Go can help us to spot race conditions with its built in [_race detector_][godoc_race_detector].
To enable this feature, run the tests with the `race` flag: `go test -race`.

You should get some output that looks like this:

```sh
==================
WARNING: DATA RACE
Write at 0x00c420084d20 by goroutine 8:
  runtime.mapassign_faststr()
      /usr/local/Cellar/go/1.9.3/libexec/src/runtime/hashmap_fast.go:774 +0x0
  github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker.func1()
      /Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12 +0x82

Previous write at 0x00c420084d20 by goroutine 7:
  runtime.mapassign_faststr()
      /usr/local/Cellar/go/1.9.3/libexec/src/runtime/hashmap_fast.go:774 +0x0
  github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker.func1()
      /Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12 +0x82

Goroutine 8 (running) created at:
  github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker()
      /Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11 +0xc4
  github.com/gypsydave5/learn-go-with-tests/concurrency/v3.TestWebsiteChecker()
      /Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker_test.go:27 +0xad
  testing.tRunner()
      /usr/local/Cellar/go/1.9.3/libexec/src/testing/testing.go:746 +0x16c

Goroutine 7 (finished) created at:
  github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker()
      /Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11 +0xc4
  github.com/gypsydave5/learn-go-with-tests/concurrency/v3.TestWebsiteChecker()
      /Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker_test.go:27 +0xad
  testing.tRunner()
      /usr/local/Cellar/go/1.9.3/libexec/src/testing/testing.go:746 +0x16c
==================
```

The details are, again, hard to read - but `WARNING: DATA RACE` is pretty
unambiguous. Reading into the body of the error we can see two different
goroutines performing writes on a map:

`Write at 0x00c420084d20 by goroutine 8:`

is writing to the same block of memory as

`Previous write at 0x00c420084d20 by goroutine 7:`

On top of that, we can see the line of code where the write is happening:

`/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12`

and the line of code where goroutines 7 and 8 are started:

`/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11`

Everything you need to know is printed to your terminal - all you have to do is
be patient enough to read it.

### Channels

We can solve this data race by coordinating our goroutines using _channels_.
Channels are a Go data structure that can both receive and send values. These
operations, along with their details, allow communication between different
processes.

In this case we want to think about the communication between the parent process
and each of the goroutines that it makes to do the work of running the
`WebsiteChecker` function with the url.

```go
package concurrency

type WebsiteChecker func(string) bool
type result struct {
	string
	bool
}

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
	results := make(map[string]bool)
	resultChannel := make(chan result)

	for _, url := range urls {
		go func() {
			resultChannel <- result{url, wc(url)}
		}()
	}

	for i := 0; i < len(urls); i++ {
		r := <-resultChannel
		results[r.string] = r.bool
	}

	return results
}
```

Alongside the `results` map we now have a `resultChannel`, which we `make` in
the same way. `chan result` is the type of the channel - a channel of `result`.
The new type, `result` has been made to associate the return value of the
`WebsiteChecker` with the url being checked - it's a struct of `string` and
`bool`. As we don't need either value to be named, each of them is anonymous
within the struct; this can be useful when it's hard to know what to name
a value.

Now when we iterate over the urls, instead of writing to the `map` directly
we're sending a `result` struct for each call to `wc` to the `resultChannel`
with a _send statement_. This uses the `<-` operator, taking a channel on the
left and a value on the right:

```go
// Send statement
resultChannel <- result{url, wc(url)}
```

The next `for` loop iterates once for each of the urls. Inside we're using
a _receive expression_, which assigns a value received from a channel to
a variable. This also uses the `<-` operator, but with the two operands now
reversed: the channel is now on the right and the variable that
we're assigning to is on the left:

```go
// Receive expression
r := <-resultChannel
```

We then use the `result` received to update the map.

By sending the results into a channel, we can control the timing of each write
into the results map, ensuring that it happens one at a time. Although each of
the calls of `wc`, and each send to the result channel, is happening concurrently
inside its own process, each of the results is being dealt with one at a time as
we take values out of the result channel with the receive expression.

We have used concurrency for the part of the code that we wanted to make faster, while
making sure that the part that cannot happen simultaneously still happens linearly.
And we have communicated across the multiple processes involved by using
channels.

When we run the benchmark:

```sh
pkg: github.com/gypsydave5/learn-go-with-tests/concurrency/v2
BenchmarkCheckWebsites-8             100          23406615 ns/op
PASS
ok      github.com/gypsydave5/learn-go-with-tests/concurrency/v2        2.377s
```
23406615 nanoseconds - 0.023 seconds, about one hundred times as fast as
original function. A great success.

## Wrapping up

This exercise has been a little lighter on the TDD than usual. In a way we've
been taking part in one long refactoring of the `CheckWebsites` function; the
inputs and outputs never changed, it just got faster. But the tests we had in
place, as well as the benchmark we wrote, allowed us to refactor `CheckWebsites`
in a way that maintained confidence that the software was still working, while
demonstrating that it had actually become faster.

In making it faster we learned about

- *goroutines*, the basic unit of concurrency in Go, which let us manage more
  than one website check request.
- *anonymous functions*, which we used to start each of the concurrent processes
  that check websites.
- *channels*, to help organize and control the communication between the
  different processes, allowing us to avoid a *race condition* bug.
- *the race detector* which helped us debug problems with concurrent code

### Make it fast

One formulation of an agile way of building software, often misattributed to Kent
Beck, is:

> [Make it work, make it right, make it fast][wrf]

Where 'work' is making the tests pass, 'right' is refactoring the code, and
'fast' is optimizing the code to make it, for example, run quickly. We can only
'make it fast' once we've made it work and made it right. We were lucky that the
code we were given was already demonstrated to be working, and didn't need to be
refactored. We should never try to 'make it fast' before the other two steps
have been performed because

> [Premature optimization is the root of all evil][popt]
> -- Donald Knuth

[DI]: dependency-injection.md
[wrf]: http://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
[godoc_race_detector]: https://blog.golang.org/race-detector
[popt]: http://wiki.c2.com/?PrematureOptimization


================================================
FILE: context/v1/context.go
================================================
package context1

import (
	"fmt"
	"net/http"
)

// Store fetches data.
type Store interface {
	Fetch() string
}

// Server returns a handler for calling Store.
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, store.Fetch())
	}
}


================================================
FILE: context/v1/context_test.go
================================================
package context1

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

type StubStore struct {
	response string
}

func (s *StubStore) Fetch() string {
	return s.response
}

func TestServer(t *testing.T) {
	data := "hello, world"
	svr := Server(&StubStore{data})

	request := httptest.NewRequest(http.MethodGet, "/", nil)
	response := httptest.NewRecorder()

	svr.ServeHTTP(response, request)

	if response.Body.String() != data {
		t.Errorf(`got "%s", want "%s"`, response.Body.String(), data)
	}
}


================================================
FILE: context/v2/context.go
================================================
package context2

import (
	"fmt"
	"net/http"
)

// Store fetches data.
type Store interface {
	Fetch() string
	Cancel()
}

// Server returns a handler for calling Store.
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		data := make(chan string, 1)

		go func() {
			data <- store.Fetch()
		}()

		select {
		case d := <-data:
			fmt.Fprint(w, d)
		case <-ctx.Done():
			store.Cancel()
		}
	}
}


================================================
FILE: context/v2/context_test.go
================================================
package context2

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

func TestServer(t *testing.T) {
	data := "hello, world"

	t.Run("returns data from store", func(t *testing.T) {
		store := &SpyStore{response: data, t: t}
		svr := Server(store)

		request := httptest.NewRequest(http.MethodGet, "/", nil)
		response := httptest.NewRecorder()

		svr.ServeHTTP(response, request)

		if response.Body.String() != data {
			t.Errorf(`got "%s", want "%s"`, response.Body.String(), data)
		}

		store.assertWasNotCancelled()
	})

	t.Run("tells store to cancel work if request is cancelled", func(t *testing.T) {
		store := &SpyStore{response: data, t: t}
		svr := Server(store)

		request := httptest.NewRequest(http.MethodGet, "/", nil)

		cancellingCtx, cancel := context.WithCancel(request.Context())
		time.AfterFunc(5*time.Millisecond, cancel)
		request = request.WithContext(cancellingCtx)

		response := httptest.NewRecorder()

		svr.ServeHTTP(response, request)

		store.assertWasCancelled()
	})
}


================================================
FILE: context/v2/testdoubles.go
================================================
package context2

import (
	"testing"
	"time"
)

// SpyStore allows you to simulate a store and see how its used.
type SpyStore struct {
	response  string
	cancelled bool
	t         *testing.T
}

// Fetch returns response after a short delay.
func (s *SpyStore) Fetch() string {
	time.Sleep(100 * time.Millisecond)
	return s.response
}

// Cancel will record the call.
func (s *SpyStore) Cancel() {
	s.cancelled = true
}

func (s *SpyStore) assertWasCancelled() {
	s.t.Helper()
	if !s.cancelled {
		s.t.Error("store was not told to cancel")
	}
}

func (s *SpyStore) assertWasNotCancelled() {
	s.t.Helper()
	if s.cancelled {
		s.t.Error("store was told to cancel")
	}
}


================================================
FILE: context/v3/context.go
================================================
package context3

import (
	"context"
	"fmt"
	"net/http"
)

// Store fetches data.
type Store interface {
	Fetch(ctx context.Context) (string, error)
}

// Server returns a handler for calling Store.
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		data, err := store.Fetch(r.Context())

		if err != nil {
			return // todo: log error however you like
		}

		fmt.Fprint(w, data)
	}
}


================================================
FILE: context/v3/context_test.go
================================================
package context3

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

func TestServer(t *testing.T) {
	data := "hello, world"

	t.Run("returns data from store", func(t *testing.T) {
		store := &SpyStore{response: data}
		svr := Server(store)

		request := httptest.NewRequest(http.MethodGet, "/", nil)
		response := httptest.NewRecorder()

		svr.ServeHTTP(response, request)

		if response.Body.String() != data {
			t.Errorf(`got "%s", want "%s"`, response.Body.String(), data)
		}
	})

	t.Run("tells store to cancel work if request is cancelled", func(t *testing.T) {
		store := &SpyStore{response: data}
		svr := Server(store)

		request := httptest.NewRequest(http.MethodGet, "/", nil)

		cancellingCtx, cancel := context.WithCancel(request.Context())
		time.AfterFunc(5*time.Millisecond, cancel)
		request = request.WithContext(cancellingCtx)

		response := &SpyResponseWriter{}

		svr.ServeHTTP(response, request)

		if response.written {
			t.Error("a response should not have been written")
		}
	})
}


================================================
FILE: context/v3/testdoubles.go
================================================
package context3

import (
	"context"
	"errors"
	"log"
	"net/http"
	"time"
)

// SpyStore allows you to simulate a store and see how its used.
type SpyStore struct {
	response string
}

// Fetch returns response after a short delay.
func (s *SpyStore) Fetch(ctx context.Context) (string, error) {
	data := make(chan string, 1)

	go func() {
		var result string
		for _, c := range s.response {
			select {
			case <-ctx.Done():
				log.Println("spy store got cancelled")
				return
			default:
				time.Sleep(10 * time.Millisecond)
				result += string(c)
			}
		}
		data <- result
	}()

	select {
	case <-ctx.Done():
		return "", ctx.Err()
	case res := <-data:
		return res, nil
	}
}

// SpyResponseWriter checks whether a response has been written.
type SpyResponseWriter struct {
	written bool
}

// Header will mark written to true.
func (s *SpyResponseWriter) Header() http.Header {
	s.written = true
	return nil
}

// Write will mark written to true.
func (s *SpyResponseWriter) Write([]byte) (int, error) {
	s.written = true
	return 0, errors.New("not implemented")
}

// WriteHeader will mark written to true.
func (s *SpyResponseWriter) WriteHeader(statusCode int) {
	s.written = true
}


================================================
FILE: context-aware-reader.md
================================================
# Context-aware readers

**[You can find all the code here](https://github.com/quii/learn-go-with-tests/tree/main/q-and-a/context-aware-reader)**

This chapter demonstrates how to test-drive a context aware `io.Reader` as written by Mat Ryer and David Hernandez in [The Pace Dev Blog](https://pace.dev/blog/2020/02/03/context-aware-ioreader-for-golang-by-mat-ryer).

## Context aware reader?

First of all, a quick primer on `io.Reader`.

If you've read other chapters in this book you will have ran into `io.Reader` when we've opened files, encoded JSON and various other common tasks. It's a simple abstraction over reading data from _something_

```go
type Reader interface {
	Read(p []byte) (n int, err error)
}
```

By using `io.Reader` you can gain a lot of re-use from the standard library, it's a very commonly used abstraction (along with its counterpart `io.Writer`)

### Context aware?

[In a previous chapter](context.md) we discussed how we can use `context` to provide cancellation. This is especially useful if you're performing tasks which may be computationally expensive and you want to be able to stop them.

When you're using an `io.Reader` you have no guarantees over speed, it could take 1 nanosecond or hundreds of hours. You might find it useful to be able to cancel these kind of tasks in your own application and that's what Mat and David wrote about.

They combined two simple abstractions (`context.Context` and `io.Reader`) to solve this problem.

Let's try and TDD some functionality so that we can wrap an `io.Reader` so it can be cancelled.

Testing this poses an interesting challenge. Normally when using an `io.Reader` you're usually supplying it to some other function and you don't really concern yourself with the details; such as `json.NewDecoder` or `io.ReadAll`.

What we want to demonstrate is something like

> Given an `io.Reader` with "ABCDEF", when I send a cancel signal half-way through I when I try to continue to read I get nothing else so all I get is "ABC"

Let's look at the interface again.

```go
type Reader interface {
	Read(p []byte) (n int, err error)
}
```

The `Reader`'s `Read` method will read the contents it has into a `[]byte` that we supply.

So rather than reading everything, we could:

 - Supply a fixed-size byte array that doesn't fit all the contents
 - Send a cancel signal
 - Try and read again and this should return an error with 0 bytes read

For now, let's just write a "happy path" test where there is no cancellation, just so we can get familiar with the problem without having to write any production code yet.

```go
func TestContextAwareReader(t *testing.T) {
	t.Run("lets just see how a normal reader works", func(t *testing.T) {
		rdr := strings.NewReader("123456")
		got := make([]byte, 3)
		_, err := rdr.Read(got)

		if err != nil {
			t.Fatal(err)
		}

		assertBufferHas(t, got, "123")

		_, err = rdr.Read(got)

		if err != nil {
			t.Fatal(err)
		}

		assertBufferHas(t, got, "456")
	})
}

func assertBufferHas(t testing.TB, buf []byte, want string) {
	t.Helper()
	got := string(buf)
	if got != want {
		t.Errorf("got %q, want %q", got, want)
	}
}
```

- Make an `io.Reader` from a string with some data
- A byte array to read into which is smaller than the contents of the reader
- Call read, check the contents, repeat.

From this we can imagine sending some kind of cancel signal before the second read to change behaviour.

Now we've seen how it works we'll TDD the rest of the functionality.

## Write the test first

We want to be able to compose an `io.Reader` with a `context.Context`.

With TDD it's best to start with imagining your desired API and write a test for it.

From there let the compiler and failing test output can guide us to a solution

```go
t.Run("behaves like a normal reader", func(t *testing.T) {
	rdr := NewCancellableReader(strings.NewReader("123456"))
	got := make([]byte, 3)
	_, err := rdr.Read(got)

	if err != nil {
		t.Fatal(err)
	}

	assertBufferHas(t, got, "123")

	_, err = rdr.Read(got)

	if err != nil {
		t.Fatal(err)
	}

	assertBufferHas(t, got, "456")
})
```

## Try to run the test

```
./cancel_readers_test.go:12:10: undefined: NewCancellableReader
```
## Write the minimal amount of code for the test to run and check the failing test output

We'll need to define this function and it should return an `io.Reader`

```go
func NewCancellableReader(rdr io.Reader) io.Reader {
	return nil
}
```

If you try and run it

```
=== RUN   TestCancelReaders
=== RUN   TestCancelReaders/behaves_like_a_normal_reader
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10f8fb5]
```

As expected

## Write enough code to make it pass

For now, we'll just return the `io.Reader` we pass in

```go
func NewCancellableReader(rdr io.Reader) io.Reader {
	return rdr
}
```

The test should now pass.

I know, I know, this seems silly and pedantic but before charging in to the fancy work it is important that we have _some_ verification that we haven't broken the "normal" behaviour of an `io.Reader` and this test will give us confidence as we move forward.

## Write the test first

Next we need to try and cancel.

```go
t.Run("stops reading when cancelled", func(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	rdr := NewCancellableReader(ctx, strings.NewReader("123456"))
	got := make([]byte, 3)
	_, err := rdr.Read(got)

	if err != nil {
		t.Fatal(err)
	}

	assertBufferHas(t, got, "123")

	cancel()

	n, err := rdr.Read(got)

	if err == nil {
		t.Error("expected an error after cancellation but didn't get one")
	}

	if n > 0 {
		t.Errorf("expected 0 bytes to be read after cancellation but %d were read", n)
	}
})
```

We can more or less copy the first test but now we're:
- Creating a `context.Context` with cancellation so we can `cancel` after the first read
- For our code to work we'll need to pass `ctx` to our function
- We then assert that post-`cancel` nothing was read

## Try to run the test

```
./cancel_readers_test.go:33:30: too many arguments in call to NewCancellableReader
	have (context.Context, *strings.Reader)
	want (io.Reader)
```

## Write the minimal amount of code for the test to run and check the failing test output

The compiler is telling us what to do; update our signature to accept a context

```go
func NewCancellableReader(ctx context.Context, rdr io.Reader) io.Reader {
	return rdr
}
```

(You'll need to update the first test to pass in `context.Background` too)

You should now see a very clear failing test output

```
=== RUN   TestCancelReaders
=== RUN   TestCancelReaders/stops_reading_when_cancelled
--- FAIL: TestCancelReaders (0.00s)
    --- FAIL: TestCancelReaders/stops_reading_when_cancelled (0.00s)
        cancel_readers_test.go:48: expected an error but didn't get one
        cancel_readers_test.go:52: expected 0 bytes to be read after cancellation but 3 were read
```

## Write enough code to make it pass

At this point, it's copy and paste from the original post by Mat and David but we'll still take it slowly and iteratively.

We know we need to have a type that encapsulates the `io.Reader` that we read from and the `context.Context` so let's create that and try and return it from our function instead of the original `io.Reader`

```go
func NewCancellableReader(ctx context.Context, rdr io.Reader) io.Reader {
	return &readerCtx{
		ctx:      ctx,
		delegate: rdr,
	}
}

type readerCtx struct {
	ctx      context.Context
	delegate io.Reader
}
```

As I have stressed many times in this book, go slowly and let the compiler help you

```
./cancel_readers_test.go:60:3: cannot use &readerCtx literal (type *readerCtx) as type io.Reader in return argument:
	*readerCtx does not implement io.Reader (missing Read method)
```

The abstraction feels right, but it doesn't implement the interface we need (`io.Reader`) so let's add the method.

```go
func (r *readerCtx) Read(p []byte) (n int, err error) {
	panic("implement me")
}
```

Run the tests and they should _compile_ but panic. This is still progress.

Let's make the first test pass by just _delegating_ the call to our underlying `io.Reader`

```go
func (r readerCtx) Read(p []byte) (n int, err error) {
	return r.delegate.Read(p)
}
```

At this point we have our happy path test passing again and it feels like we have our stuff abstracted nicely

To make our second test pass we need to check the `context.Context` to see if it has been cancelled.

```go
func (r readerCtx) Read(p []byte) (n int, err error) {
	if err := r.ctx.Err(); err != nil {
		return 0, err
	}
	return r.delegate.Read(p)
}
```

All tests should now pass. You'll notice how we return the error from the `context.Context`. This allows callers of the code to inspect the various reasons cancellation has occurred and this is covered more in the original post.

## Wrapping up

- Small interfaces are good and are easily composed
- When you're trying to augment one thing (e.g `io.Reader`) with another you usually want to reach for the [delegation pattern](https://en.wikipedia.org/wiki/Delegation_pattern)

> In software engineering, the delegation pattern is an object-oriented design pattern that allows object composition to achieve the same code reuse as inheritance.

- An easy way to start this kind of work is to wrap your delegate and write a test that asserts it behaves how the delegate normally does before you start composing other parts to change behaviour. This will help you to keep things working correctly as you code toward your goal


================================================
FILE: context.md
================================================
# Context

**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main/context)**

Software often kicks off long-running, resource-intensive processes (often in goroutines). If the action that caused this gets cancelled or fails for some reason you need to stop these processes in a consistent way through your application.

If you don't manage this your snappy Go application that you're so proud of could start having difficult to debug performance problems.

In this chapter we'll use the package `context` to help us manage long-running processes.

We're going to start with a classic example of a web server that when hit kicks off a potentially long-running process to fetch some data for it to return in the response.

We will exercise a scenario where a user cancels the request before the data can be retrieved and we'll make sure the process is told to give up.

I've set up some code on the happy path to get us started. Here is our server code.

```go
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, store.Fetch())
	}
}
```

The function `Server` takes a `Store` and returns us a `http.HandlerFunc`. Store is defined as:

```go
type Store interface {
	Fetch() string
}
```

The returned function calls the `store`'s `Fetch` method to get the data and writes it to the response.

We have a corresponding spy for `Store` which we use in a test.

```go
type SpyStore struct {
	response string
}

func (s *SpyStore) Fetch() string {
	return s.response
}

func TestServer(t *testing.T) {
	data := "hello, world"
	svr := Server(&SpyStore{data})

	request := httptest.NewRequest(http.MethodGet, "/", nil)
	response := httptest.NewRecorder()

	svr.ServeHTTP(response, request)

	if response.Body.String() != data {
		t.Errorf(`got "%s", want "%s"`, response.Body.String(), data)
	}
}
```

Now that we have a happy path, we want to make a more realistic scenario where the `Store` can't finish a`Fetch` before the user cancels the request.

## Write the test first

Our handler will need a way of telling the `Store` to cancel the work so update the interface.

```go
type Store interface {
	Fetch() string
	Cancel()
}
```

We will need to adjust our spy so it takes some time to return `data` and a way of knowing it has been told to cancel. It'll have to add `Cancel` as a method to implement the `Store` interface.

```go
type SpyStore struct {
	response  string
	cancelled bool
}

func (s *SpyStore) Fetch() string {
	time.Sleep(100 * time.Millisecond)
	return s.response
}

func (s *SpyStore) Cancel() {
	s.cancelled = true
}
```

Let's add a new test where we cancel the request before 100 milliseconds and check the store to see if it gets cancelled.

```go
t.Run("tells store to cancel work if request is cancelled", func(t *testing.T) {
	data := "hello, world"
	store := &SpyStore{response: data}
	svr := Server(store)

	request := httptest.NewRequest(http.MethodGet, "/", nil)

	cancellingCtx, cancel := context.WithCancel(request.Context())
	time.AfterFunc(5*time.Millisecond, cancel)
	request = request.WithContext(cancellingCtx)

	response := httptest.NewRecorder()

	svr.ServeHTTP(response, request)

	if !store.cancelled {
		t.Error("store was not told to cancel")
	}
})
```

From the [Go Blog: Context](https://blog.golang.org/context)

> The context package provides functions to derive new Context values from existing ones. These values form a tree: when a Context is canceled, all Contexts derived from it are also canceled.

It's important that you derive your contexts so that cancellations are propagated throughout the call stack for a given request.

What we do is derive a new `cancellingCtx` from our `request` which returns us a `cancel` function. We then schedule that function to be called in 5 milliseconds by using `time.AfterFunc`. Finally we use this new context in our request by calling `request.WithContext`.

## Try to run the test

The test fails as we'd expect.

```
--- FAIL: TestServer (0.00s)
    --- FAIL: TestServer/tells_store_to_cancel_work_if_request_is_cancelled (0.00s)
    	context_test.go:62: store was not told to cancel
```

## Write enough code to make it pass

Remember to be disciplined with TDD. Write the _minimal_ amount of code to make our test pass.

```go
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		store.Cancel()
		fmt.Fprint(w, store.Fetch())
	}
}
```

This makes this test pass but it doesn't feel good does it! We surely shouldn't be cancelling `Cancel()` before we fetch on _every request_.

By being disciplined it highlighted a flaw in our tests, this is a good thing!

We'll need to update our happy path test to assert that it does not get cancelled.

```go
t.Run("returns data from store", func(t *testing.T) {
	data := "hello, world"
	store := &SpyStore{response: data}
	svr := Server(store)

	request := httptest.NewRequest(http.MethodGet, "/", nil)
	response := httptest.NewRecorder()

	svr.ServeHTTP(response, request)

	if response.Body.String() != data {
		t.Errorf(`got "%s", want "%s"`, response.Body.String(), data)
	}

	if store.cancelled {
		t.Error("it should not have cancelled the store")
	}
})
```

Run both tests and the happy path test should now be failing and now we're forced to do a more sensible implementation.

```go
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		data := make(chan string, 1)

		go func() {
			data <- store.Fetch()
		}()

		select {
		case d := <-data:
			fmt.Fprint(w, d)
		case <-ctx.Done():
			store.Cancel()
		}
	}
}
```

What have we done here?

`context` has a method `Done()` which returns a channel which gets sent a signal when the context is "done" or "cancelled". We want to listen to that signal and call `store.Cancel` if we get it but we want to ignore it if our `Store` manages to `Fetch` before it.

To manage this we run `Fetch` in a goroutine and it will write the result into a new channel `data`. We then use `select` to effectively race to the two asynchronous processes and then we either write a response or `Cancel`.

## Refactor

We can refactor our test code a bit by making assertion methods on our spy

```go
type SpyStore struct {
	response  string
	cancelled bool
	t         *testing.T
}

func (s *SpyStore) assertWasCancelled() {
	s.t.Helper()
	if !s.cancelled {
		s.t.Error("store was not told to cancel")
	}
}

func (s *SpyStore) assertWasNotCancelled() {
	s.t.Helper()
	if s.cancelled {
		s.t.Error("store was told to cancel")
	}
}
```

Remember to pass in the `*testing.T` when creating the spy.

```go
func TestServer(t *testing.T) {
	data := "hello, world"

	t.Run("returns data from store", func(t *testing.T) {
		store := &SpyStore{response: data, t: t}
		svr := Server(store)

		request := httptest.NewRequest(http.MethodGet, "/", nil)
		response := httptest.NewRecorder()

		svr.ServeHTTP(response, request)

		if response.Body.String() != data {
			t.Errorf(`got "%s", want "%s"`, response.Body.String(), data)
		}

		store.assertWasNotCancelled()
	})

	t.Run("tells store to cancel work if request is cancelled", func(t *testing.T) {
		store := &SpyStore{response: data, t: t}
		svr := Server(store)

		request := httptest.NewRequest(http.MethodGet, "/", nil)

		cancellingCtx, cancel := context.WithCancel(request.Context())
		time.AfterFunc(5*time.Millisecond, cancel)
		request = request.WithContext(cancellingCtx)

		response := httptest.NewRecorder()

		svr.ServeHTTP(response, request)

		store.assertWasCancelled()
	})
}
```

This approach is ok, but is it idiomatic?

Does it make sense for our web server to be concerned with manually cancelling `Store`? What if `Store` also happens to depend on other slow-running processes? We'll have to make sure that `Store.Cancel` correctly propagates the cancellation to all of its dependants.

One of the main points of `context` is that it is a consistent way of offering cancellation.

[From the go doc](https://golang.org/pkg/context/)

> Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. When a Context is canceled, all Contexts derived from it are also canceled.

From the [Go Blog: Context](https://blog.golang.org/context) again:

> At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests. This allows Go code developed by many different teams to interoperate well. It provides simple control over timeouts and cancellation and ensures that critical values like security credentials transit Go programs properly.

(Pause for a moment and think of the ramifications of every function having to send in a context, and the ergonomics of that.)

Feeling a bit uneasy? Good. Let's try and follow that approach though and instead pass through the `context` to our `Store` and let it be responsible. That way it can also pass the `context` through to its dependants and they too can be responsible for stopping themselves.

## Write the test first

We'll have to change our existing tests as their responsibilities are changing. The only thing our handler is responsible for now is making sure it sends a context through to the downstream `Store` and that it handles the error that will come from the `Store` when it is cancelled.

Let's update our `Store` interface to show the new responsibilities.

```go
type Store interface {
	Fetch(ctx context.Context) (string, error)
}
```

Delete the code inside our handler for now

```go
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
	}
}
```

Update our `SpyStore`

```go
type SpyStore struct {
	response string
	t        *testing.T
}

func (s *SpyStore) Fetch(ctx context.Context) (string, error) {
	data := make(chan string, 1)

	go func() {
		var result string
		for _, c := range s.response {
			select {
			case <-ctx.Done():
				log.Println("spy store got cancelled")
				return
			default:
				time.Sleep(10 * time.Millisecond)
				result += string(c)
			}
		}
		data <- result
	}()

	select {
	case <-ctx.Done():
		return "", ctx.Err()
	case res := <-data:
		return res, nil
	}
}
```

We have to make our spy act like a real method that works with `context`.

We are simulating a slow process where we build the result slowly by appending the string, character by character in a goroutine. When the goroutine finishes its work it writes the string to the `data` channel. The goroutine listens for the `ctx.Done` and will stop the work if a signal is sent in that channel.

Finally the code uses another `select` to wait for that goroutine to finish its work or for the cancellation to occur.

It's similar to our approach from before, we use Go's concurrency primitives to make two asynchronous processes race each other to determine what we return.

You'll take a similar approach when writing your own functions and methods that accept a `context` so make sure you understand what's going on.

Finally we can update our tests. Comment out our cancellation test so we can fix the happy path test first.

```go
t.Run("returns data from store", func(t *testing.T) {
	data := "hello, world"
	store := &SpyStore{response: data, t: t}
	svr := Server(store)

	request := httptest.NewRequest(http.MethodGet, "/", nil)
	response := httptest.NewRecorder()

	svr.ServeHTTP(response, request)

	if response.Body.String() != data {
		t.Errorf(`got "%s", want "%s"`, response.Body.String(), data)
	}
})
```

## Try to run the test

```
=== RUN   TestServer/returns_data_from_store
--- FAIL: TestServer (0.00s)
    --- FAIL: TestServer/returns_data_from_store (0.00s)
    	context_test.go:22: got "", want "hello, world"
```

## Write enough code to make it pass

```go
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		data, _ := store.Fetch(r.Context())
		fmt.Fprint(w, data)
	}
}
```

Our happy path should be... happy. Now we can fix the other test.

## Write the test first

We need to test that we do not write any kind of response on the error case. Sadly `httptest.ResponseRecorder` doesn't have a way of figuring this out so we'll have to roll our own spy to test for this.

```go
type SpyResponseWriter struct {
	written bool
}

func (s *SpyResponseWriter) Header() http.Header {
	s.written = true
	return nil
}

func (s *SpyResponseWriter) Write([]byte) (int, error) {
	s.written = true
	return 0, errors.New("not implemented")
}

func (s *SpyResponseWriter) WriteHeader(statusCode int) {
	s.written = true
}
```

Our `SpyResponseWriter` implements `http.ResponseWriter` so we can use it in the test.

```go
t.Run("tells store to cancel work if request is cancelled", func(t *testing.T) {
	data := "hello, world"
	store := &SpyStore{response: data, t: t}
	svr := Server(store)

	request := httptest.NewRequest(http.MethodGet, "/", nil)

	cancellingCtx, cancel := context.WithCancel(request.Context())
	time.AfterFunc(5*time.Millisecond, cancel)
	request = request.WithContext(cancellingCtx)

	response := &SpyResponseWriter{}

	svr.ServeHTTP(response, request)

	if response.written {
		t.Error("a response should not have been written")
	}
})
```

## Try to run the test

```
=== RUN   TestServer
=== RUN   TestServer/tells_store_to_cancel_work_if_request_is_cancelled
--- FAIL: TestServer (0.01s)
    --- FAIL: TestServer/tells_store_to_cancel_work_if_request_is_cancelled (0.01s)
    	context_test.go:47: a response should not have been written
```

## Write enough code to make it pass

```go
func Server(store Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		data, err := store.Fetch(r.Context())

		if err != nil {
			return // todo: log error however you like
		}

		fmt.Fprint(w, data)
	}
}
```

We can see after this that the server code has become simplified as it's no longer explicitly responsible for cancellation, it simply passes through `context` and relies on the downstream functions to respect any cancellations that may occur.

## Wrapping up

### What we've covered

- How to test a HTTP handler that has had the request cancelled by the client.
- How to use context to manage cancellation.
- How to write a function that accepts `context` and uses it to cancel itself by using goroutines, `select` and channels.
- Follow Google's guidelines as to how to manage cancellation by propagating request scoped context through your call-stack.
- How to roll your own spy for `http.ResponseWriter` if you need it.

### What about context.Value ?

[Michal Štrba](https://faiface.github.io/post/context-should-go-away-go2/) and I have a similar opinion.

> If you use ctx.Value in my (non-existent) company, you’re fired

Some engineers have advocated passing values through `context` as it _feels convenient_.

Convenience is often the cause of bad code.

The problem with `context.Values` is that it's just an untyped map so you have no type-safety and you have to handle it not actually containing your value. You have to create a coupling of map keys from one module to another and if someone changes something things start breaking.

In short, **if a function needs some values, put them as typed parameters rather than trying to fetch them from `context.Value`**. This makes it statically checked and documented for everyone to see.

#### But...

On other hand, it can be helpful to include information that is orthogonal to a request in a context, such as a trace id. Potentially this information would not be needed by every function in your call-stack and would make your functional signatures very messy.

[Jack Lindamood says **Context.Value should inform, not control**](https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39)

> The content of context.Value is for maintainers not users. It should never be required input for documented or expected results.

### Additional material

- I really enjoyed reading [Context should go away for Go 2 by Michal Štrba](https://faiface.github.io/post/context-should-go-away-go2/). His argument is that having to pass `context` everywhere is a smell, that it's pointing to a deficiency in the language in respect to cancellation. He says it would better if this was somehow solved at the language level, rather than at a library level. Until that happens, you will need `context` if you want to manage long running processes.
- The [Go blog further describes the motivation for working with `context` and has some examples](https://blog.golang.org/context)


================================================
FILE: contributing.md
================================================
# Contributing

Contributions are very welcome. I hope for this to become a great home for guides of how to learn Go by writing tests. Consider submitting a PR or creating an issue which you can do [here](https://github.com/quii/learn-go-with-tests/issues).

## What we're looking for

* Teaching Go features \(e.g things like `if`, `select`, structs, methods, etc\).
* Showcase interesting functionality within the standard library. Show off how easy it is to TDD a HTTP server for instance.
* Show how Go's tooling, like benchmarking, race detectors, etc can help you arrive at great software.

If you don't feel confident to submit your own guide, submitting an issue for something you want to learn is still a valuable contribution.

### ⚠️ Get feedback quickly for new content ⚠️

- TDD teaches us to work iteratively and get feedback and I strongly suggest you do the same if you wish to contribute
    - Open a PR with your first test and implementation, discuss your approach so I can offer feedback and course correct
- This is of course open-source but I do have strong opinions on the content. The sooner you talk to me the better.

## Style guide

* Always be reinforcing the TDD cycle. Take a look at the [Chapter Template](template.md).
* Emphasis on iterating over functionality driven by tests. The Hello, world example works well because we gradually make it more sophisticated and learning new techniques _driven_ by the tests. For example:
  * `Hello()` &lt;- how to write functions, return types.
  * `Hello(name string)` &lt;- arguments, constants.
  * `Hello(name string)` &lt;- default to "world" using `i
Download .txt
gitextract_n1nyvkdi/

├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── go.yml
├── .gitignore
├── .mdlrc
├── LICENSE.md
├── README.md
├── SUMMARY.md
├── anti-patterns.md
├── app-intro.md
├── arrays/
│   ├── v1/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v2/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v3/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v4/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v5/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v6/
│   │   ├── sum.go
│   │   └── sum_test.go
│   ├── v7/
│   │   ├── sum.go
│   │   └── sum_test.go
│   └── v8/
│       ├── assert.go
│       ├── bad_bank.go
│       ├── bad_bank_test.go
│       ├── collection_fun.go
│       ├── sum.go
│       └── sum_test.go
├── arrays-and-slices.md
├── blogrenderer/
│   ├── post.go
│   ├── renderer.go
│   ├── renderer_test.TestRender.it_converts_a_single_post_into_HTML.approved.txt
│   ├── renderer_test.TestRender.it_renders_an_index_of_posts.approved.txt
│   ├── renderer_test.go
│   └── templates/
│       ├── blog.gohtml
│       ├── bottom.gohtml
│       ├── index.gohtml
│       └── top.gohtml
├── book.json
├── build.books.sh
├── build.sh
├── command-line/
│   ├── v1/
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   └── tape_test.go
│   ├── v2/
│   │   ├── CLI.go
│   │   ├── CLI_test.go
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   └── tape_test.go
│   └── v3/
│       ├── CLI.go
│       ├── CLI_test.go
│       ├── cmd/
│       │   ├── cli/
│       │   │   └── main.go
│       │   └── webserver/
│       │       └── main.go
│       ├── file_system_store.go
│       ├── file_system_store_test.go
│       ├── league.go
│       ├── server.go
│       ├── server_integration_test.go
│       ├── server_test.go
│       ├── tape.go
│       ├── tape_test.go
│       └── testing.go
├── command-line.md
├── concurrency/
│   ├── v1/
│   │   ├── check_website.go
│   │   ├── check_websites.go
│   │   ├── check_websites_benchmark_test.go
│   │   └── check_websites_test.go
│   ├── v2/
│   │   ├── check_website.go
│   │   ├── check_websites.go
│   │   ├── check_websites_benchmark_test.go
│   │   └── check_websites_test.go
│   └── v3/
│       ├── check_website.go
│       ├── check_websites.go
│       ├── check_websites_benchmark_test.go
│       └── check_websites_test.go
├── concurrency.md
├── context/
│   ├── v1/
│   │   ├── context.go
│   │   └── context_test.go
│   ├── v2/
│   │   ├── context.go
│   │   ├── context_test.go
│   │   └── testdoubles.go
│   └── v3/
│       ├── context.go
│       ├── context_test.go
│       └── testdoubles.go
├── context-aware-reader.md
├── context.md
├── contributing.md
├── dependency-injection.md
├── di/
│   ├── v1/
│   │   ├── di.go
│   │   └── di_test.go
│   └── v2/
│       ├── di.go
│       └── di_test.go
├── epub-cover.pxm
├── error-types.md
├── for/
│   ├── v1/
│   │   ├── repeat.go
│   │   └── repeat_test.go
│   ├── v2/
│   │   ├── repeat.go
│   │   └── repeat_test.go
│   ├── v3/
│   │   ├── repeat.go
│   │   └── repeat_test.go
│   └── vx/
│       ├── repeat.go
│       └── repeat_test.go
├── gb-readme.md
├── generics/
│   ├── assert.go
│   ├── generics_test.go
│   └── stack.go
├── generics.md
├── go.mod
├── go.sum
├── hello-world/
│   ├── v1/
│   │   └── hello.go
│   ├── v2/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v3/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v4/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v5/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v6/
│   │   ├── hello.go
│   │   └── hello_test.go
│   ├── v7/
│   │   ├── hello.go
│   │   └── hello_test.go
│   └── v8/
│       ├── hello.go
│       └── hello_test.go
├── hello-world.md
├── html-templates.md
├── http-handlers-revisited.md
├── http-server/
│   ├── v1/
│   │   ├── main.go
│   │   ├── server.go
│   │   └── server_test.go
│   ├── v2/
│   │   ├── main.go
│   │   ├── server.go
│   │   └── server_test.go
│   ├── v3/
│   │   ├── main.go
│   │   ├── server.go
│   │   └── server_test.go
│   ├── v4/
│   │   ├── main.go
│   │   ├── server.go
│   │   └── server_test.go
│   └── v5/
│       ├── in_memory_player_store.go
│       ├── main.go
│       ├── server.go
│       ├── server_integration_test.go
│       └── server_test.go
├── http-server.md
├── install-go.md
├── integers/
│   ├── v1/
│   │   ├── adder.go
│   │   └── adder_test.go
│   └── v2/
│       ├── adder.go
│       └── adder_test.go
├── integers.md
├── intro-to-acceptance-tests.md
├── io/
│   ├── v1/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── in_memory_player_store.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v2/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── in_memory_player_store.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v3/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── in_memory_player_store.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v4/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── in_memory_player_store.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v5/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v6/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v7/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   └── tape_test.go
│   ├── v8/
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   └── tape_test.go
│   └── v9/
│       ├── file_system_store.go
│       ├── file_system_store_test.go
│       ├── league.go
│       ├── main.go
│       ├── server.go
│       ├── server_integration_test.go
│       ├── server_test.go
│       ├── tape.go
│       └── tape_test.go
├── io.md
├── iteration.md
├── iterators/
│   └── iterators_test.go
├── json/
│   ├── v1/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v2/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v3/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v4/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   ├── v5/
│   │   ├── in_memory_player_store.go
│   │   ├── main.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   └── server_test.go
│   └── v6/
│       ├── in_memory_player_store.go
│       ├── main.go
│       ├── server.go
│       ├── server_integration_test.go
│       └── server_test.go
├── json.md
├── maps/
│   ├── v1/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v2/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v3/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v4/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v5/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   ├── v6/
│   │   ├── dictionary.go
│   │   └── dictionary_test.go
│   └── v7/
│       ├── dictionary.go
│       └── dictionary_test.go
├── maps.md
├── math/
│   ├── v1/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       └── clockface_test.go
│   ├── v10/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v11/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v12/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v2/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v3/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v4/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v5/
│   │   └── clockface/
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v6/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       └── clockface_test.go
│   ├── v7/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v7b/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v7c/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v8/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   ├── v9/
│   │   └── clockface/
│   │       ├── clockface/
│   │       │   └── main.go
│   │       ├── clockface.go
│   │       ├── clockface_acceptance_test.go
│   │       ├── clockface_test.go
│   │       └── svgWriter.go
│   └── vFinal/
│       └── clockface/
│           ├── clockface/
│           │   └── main.go
│           ├── clockface.go
│           ├── clockface_test.go
│           └── svg/
│               ├── svg.go
│               └── svg_test.go
├── math.md
├── meta.tmpl.tex
├── mocking/
│   ├── v1/
│   │   ├── countdown_test.go
│   │   └── main.go
│   ├── v2/
│   │   ├── countdown_test.go
│   │   └── main.go
│   ├── v3/
│   │   ├── countdown_test.go
│   │   └── main.go
│   ├── v4/
│   │   ├── countdown_test.go
│   │   └── main.go
│   ├── v5/
│   │   ├── countdown_test.go
│   │   └── main.go
│   └── v6/
│       ├── countdown_test.go
│       └── main.go
├── mocking.md
├── os-exec.md
├── pdf-cover.md
├── pdf-cover.tex
├── pointers/
│   ├── v1/
│   │   ├── wallet.go
│   │   └── wallet_test.go
│   ├── v2/
│   │   ├── wallet.go
│   │   └── wallet_test.go
│   ├── v3/
│   │   ├── wallet.go
│   │   └── wallet_test.go
│   └── v4/
│       ├── wallet.go
│       └── wallet_test.go
├── pointers-and-errors.md
├── q-and-a/
│   ├── context-aware-reader/
│   │   ├── context_aware_reader.go
│   │   └── context_aware_reader_test.go
│   ├── error-types/
│   │   ├── error-types_test.go
│   │   └── v2/
│   │       └── error-types_test.go
│   ├── http-handlers-revisited/
│   │   ├── basic_test.go
│   │   ├── still_basic.go
│   │   └── still_basic_test.go
│   └── os-exec/
│       ├── msg.xml
│       └── os-exec_test.go
├── reading-files/
│   ├── blogposts.go
│   ├── blogposts_test.go
│   └── post.go
├── reading-files.md
├── refactoring-checklist.md
├── reflection/
│   ├── v1/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v10/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v2/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v3/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v4/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v5/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v6/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v7/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   ├── v8/
│   │   ├── reflection.go
│   │   └── reflection_test.go
│   └── v9/
│       ├── reflection.go
│       └── reflection_test.go
├── reflection.md
├── revisiting-arrays-and-slices-with-generics.md
├── roman-numerals/
│   ├── v1/
│   │   └── numeral_test.go
│   ├── v10/
│   │   ├── numeral_test.go
│   │   └── roman_numerals.go
│   ├── v11/
│   │   ├── numeral_test.go
│   │   └── roman_numerals.go
│   ├── v2/
│   │   └── numeral_test.go
│   ├── v3/
│   │   └── numeral_test.go
│   ├── v4/
│   │   └── numeral_test.go
│   ├── v5/
│   │   └── numeral_test.go
│   ├── v6/
│   │   └── numeral_test.go
│   ├── v7/
│   │   └── numeral_test.go
│   ├── v8/
│   │   └── numeral_test.go
│   └── v9/
│       └── numeral_test.go
├── roman-numerals.md
├── scaling-acceptance-tests.md
├── select/
│   ├── v1/
│   │   ├── racer.go
│   │   └── racer_test.go
│   ├── v2/
│   │   ├── racer.go
│   │   └── racer_test.go
│   └── v3/
│       ├── racer.go
│       └── racer_test.go
├── select.md
├── structs/
│   ├── v1/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v2/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v3/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v4/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v5/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v6/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   ├── v7/
│   │   ├── shapes.go
│   │   └── shapes_test.go
│   └── v8/
│       ├── shapes.go
│       └── shapes_test.go
├── structs-methods-and-interfaces.md
├── sync/
│   ├── v1/
│   │   ├── sync.go
│   │   └── sync_test.go
│   └── v2/
│       ├── sync.go
│       └── sync_test.go
├── sync.md
├── template.md
├── time/
│   ├── v1/
│   │   ├── CLI.go
│   │   ├── CLI_test.go
│   │   ├── blind_alerter.go
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   ├── tape_test.go
│   │   └── testing.go
│   ├── v2/
│   │   ├── CLI.go
│   │   ├── CLI_test.go
│   │   ├── blind_alerter.go
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   ├── tape_test.go
│   │   ├── testing.go
│   │   ├── texas_holdem.go
│   │   └── texas_holdem_test.go
│   └── v3/
│       ├── BlindAlerter.go
│       ├── CLI.go
│       ├── CLI_test.go
│       ├── cmd/
│       │   ├── cli/
│       │   │   └── main.go
│       │   └── webserver/
│       │       └── main.go
│       ├── file_system_store.go
│       ├── file_system_store_test.go
│       ├── game.go
│       ├── league.go
│       ├── server.go
│       ├── server_integration_test.go
│       ├── server_test.go
│       ├── tape.go
│       ├── tape_test.go
│       ├── testing.go
│       ├── texas_holdem.go
│       └── texas_holdem_test.go
├── time.md
├── title.txt
├── todo/
│   └── todo1_test.go
├── websockets/
│   ├── v1/
│   │   ├── CLI.go
│   │   ├── CLI_test.go
│   │   ├── Gopkg.toml
│   │   ├── blind_alerter.go
│   │   ├── cmd/
│   │   │   ├── cli/
│   │   │   │   └── main.go
│   │   │   └── webserver/
│   │   │       └── main.go
│   │   ├── file_system_store.go
│   │   ├── file_system_store_test.go
│   │   ├── game.go
│   │   ├── game.html
│   │   ├── league.go
│   │   ├── server.go
│   │   ├── server_integration_test.go
│   │   ├── server_test.go
│   │   ├── tape.go
│   │   ├── tape_test.go
│   │   ├── testing.go
│   │   ├── texas_holdem.go
│   │   ├── texas_holdem_test.go
│   │   └── vendor/
│   │       └── github.com/
│   │           └── gorilla/
│   │               └── websocket/
│   │                   ├── .gitignore
│   │                   ├── .travis.yml
│   │                   ├── AUTHORS
│   │                   ├── LICENSE
│   │                   ├── README.md
│   │                   ├── client.go
│   │                   ├── client_clone.go
│   │                   ├── client_clone_legacy.go
│   │                   ├── compression.go
│   │                   ├── conn.go
│   │                   ├── conn_write.go
│   │                   ├── conn_write_legacy.go
│   │                   ├── doc.go
│   │                   ├── json.go
│   │                   ├── mask.go
│   │                   ├── mask_safe.go
│   │                   ├── prepared.go
│   │                   ├── proxy.go
│   │                   ├── server.go
│   │                   ├── trace.go
│   │                   ├── trace_17.go
│   │                   ├── util.go
│   │                   └── x_net_proxy.go
│   └── v2/
│       ├── CLI.go
│       ├── CLI_test.go
│       ├── Gopkg.toml
│       ├── blind_alerter.go
│       ├── cmd/
│       │   ├── cli/
│       │   │   └── main.go
│       │   └── webserver/
│       │       └── main.go
│       ├── file_system_store.go
│       ├── file_system_store_test.go
│       ├── game.go
│       ├── game.html
│       ├── league.go
│       ├── player_server_ws.go
│       ├── server.go
│       ├── server_integration_test.go
│       ├── server_test.go
│       ├── tape.go
│       ├── tape_test.go
│       ├── testing.go
│       ├── texas_holdem.go
│       ├── texas_holdem_test.go
│       └── vendor/
│           └── github.com/
│               └── gorilla/
│                   └── websocket/
│                       ├── .gitignore
│                       ├── .travis.yml
│                       ├── AUTHORS
│                       ├── LICENSE
│                       ├── README.md
│                       ├── client.go
│                       ├── client_clone.go
│                       ├── client_clone_legacy.go
│                       ├── compression.go
│                       ├── conn.go
│                       ├── conn_write.go
│                       ├── conn_write_legacy.go
│                       ├── doc.go
│                       ├── json.go
│                       ├── mask.go
│                       ├── mask_safe.go
│                       ├── prepared.go
│                       ├── proxy.go
│                       ├── server.go
│                       ├── trace.go
│                       ├── trace_17.go
│                       ├── util.go
│                       └── x_net_proxy.go
├── websockets.md
├── why.md
└── working-without-mocks.md
Download .txt
Showing preview only (212K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2425 symbols across 517 files)

FILE: arrays/v1/sum.go
  function Sum (line 4) | func Sum(numbers [5]int) int {

FILE: arrays/v1/sum_test.go
  function TestSum (line 5) | func TestSum(t *testing.T) {

FILE: arrays/v2/sum.go
  function Sum (line 4) | func Sum(numbers [5]int) int {

FILE: arrays/v2/sum_test.go
  function TestSum (line 5) | func TestSum(t *testing.T) {

FILE: arrays/v3/sum.go
  function Sum (line 4) | func Sum(numbers []int) int {

FILE: arrays/v3/sum_test.go
  function TestSum (line 5) | func TestSum(t *testing.T) {

FILE: arrays/v4/sum.go
  function Sum (line 4) | func Sum(numbers []int) int {
  function SumAll (line 13) | func SumAll(numbersToSum ...[]int) []int {

FILE: arrays/v4/sum_test.go
  function TestSum (line 8) | func TestSum(t *testing.T) {
  function TestSumAll (line 23) | func TestSumAll(t *testing.T) {

FILE: arrays/v5/sum.go
  function Sum (line 4) | func Sum(numbers []int) int {
  function SumAll (line 13) | func SumAll(numbersToSum ...[]int) []int {

FILE: arrays/v5/sum_test.go
  function TestSum (line 8) | func TestSum(t *testing.T) {
  function TestSumAll (line 24) | func TestSumAll(t *testing.T) {

FILE: arrays/v6/sum.go
  function Sum (line 4) | func Sum(numbers []int) int {
  function SumAllTails (line 13) | func SumAllTails(numbersToSum ...[]int) []int {

FILE: arrays/v6/sum_test.go
  function TestSum (line 8) | func TestSum(t *testing.T) {
  function TestSumAllTails (line 24) | func TestSumAllTails(t *testing.T) {

FILE: arrays/v7/sum.go
  function Sum (line 4) | func Sum(numbers []int) int {
  function SumAllTails (line 13) | func SumAllTails(numbersToSum ...[]int) []int {

FILE: arrays/v7/sum_test.go
  function TestSum (line 8) | func TestSum(t *testing.T) {
  function TestSumAllTails (line 24) | func TestSumAllTails(t *testing.T) {

FILE: arrays/v8/assert.go
  function AssertEqual (line 5) | func AssertEqual[T comparable](t *testing.T, got, want T) {
  function AssertNotEqual (line 12) | func AssertNotEqual[T comparable](t *testing.T, got, want T) {
  function AssertTrue (line 19) | func AssertTrue(t *testing.T, got bool) {
  function AssertFalse (line 26) | func AssertFalse(t *testing.T, got bool) {

FILE: arrays/v8/bad_bank.go
  type Transaction (line 3) | type Transaction struct
  function NewTransaction (line 9) | func NewTransaction(from, to Account, sum float64) Transaction {
  type Account (line 13) | type Account struct
  function NewBalanceFor (line 18) | func NewBalanceFor(account Account, transactions []Transaction) Account {
  function applyTransaction (line 26) | func applyTransaction(a Account, transaction Transaction) Account {

FILE: arrays/v8/bad_bank_test.go
  function TestBadBank (line 5) | func TestBadBank(t *testing.T) {

FILE: arrays/v8/collection_fun.go
  function Find (line 3) | func Find[A any](items []A, predicate func(A) bool) (value A, found bool) {
  function Reduce (line 12) | func Reduce[A, B any](collection []A, f func(B, A) B, initialValue B) B {

FILE: arrays/v8/sum.go
  function Sum (line 4) | func Sum(numbers []int) int {
  function SumAllTails (line 10) | func SumAllTails(numbers ...[]int) []int {

FILE: arrays/v8/sum_test.go
  function TestSum (line 9) | func TestSum(t *testing.T) {
  function TestSumAllTails (line 25) | func TestSumAllTails(t *testing.T) {
  function TestReduce (line 47) | func TestReduce(t *testing.T) {
  function TestFind (line 65) | func TestFind(t *testing.T) {

FILE: blogrenderer/post.go
  type Post (line 6) | type Post struct
    method SanitisedTitle (line 12) | func (p Post) SanitisedTitle() string {

FILE: blogrenderer/renderer.go
  type PostRenderer (line 17) | type PostRenderer struct
    method Render (line 36) | func (r *PostRenderer) Render(w io.Writer, p Post) error {
    method RenderIndex (line 41) | func (r *PostRenderer) RenderIndex(w io.Writer, posts []Post) error {
  function NewPostRenderer (line 23) | func NewPostRenderer() (*PostRenderer, error) {
  type postViewModel (line 45) | type postViewModel struct
  function newPostVM (line 50) | func newPostVM(p Post, r *PostRenderer) postViewModel {

FILE: blogrenderer/renderer_test.go
  function TestRender (line 11) | func TestRender(t *testing.T) {
  function BenchmarkRender (line 50) | func BenchmarkRender(b *testing.B) {

FILE: command-line/v1/cmd/cli/main.go
  function main (line 5) | func main() {

FILE: command-line/v1/cmd/webserver/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: command-line/v1/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 56) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 64) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 76) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function initialisePlayerDBFile (line 38) | func initialisePlayerDBFile(file *os.File) error {

FILE: command-line/v1/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 26) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 111) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 118) | func assertNoError(t testing.TB, err error) {

FILE: command-line/v1/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: command-line/v1/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: command-line/v1/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: command-line/v1/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: command-line/v1/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: command-line/v1/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: command-line/v2/CLI.go
  type CLI (line 4) | type CLI struct
    method PlayPoker (line 9) | func (cli *CLI) PlayPoker() {

FILE: command-line/v2/CLI_test.go
  function TestCLI (line 7) | func TestCLI(t *testing.T) {

FILE: command-line/v2/cmd/cli/main.go
  function main (line 5) | func main() {

FILE: command-line/v2/cmd/webserver/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: command-line/v2/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 56) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 64) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 76) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function initialisePlayerDBFile (line 38) | func initialisePlayerDBFile(file *os.File) error {

FILE: command-line/v2/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 26) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 111) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 118) | func assertNoError(t testing.TB, err error) {

FILE: command-line/v2/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: command-line/v2/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: command-line/v2/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: command-line/v2/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: command-line/v2/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: command-line/v2/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: command-line/v3/CLI.go
  type CLI (line 10) | type CLI struct
    method PlayPoker (line 24) | func (cli *CLI) PlayPoker() {
    method readLine (line 33) | func (cli *CLI) readLine() string {
  function NewCLI (line 16) | func NewCLI(store PlayerStore, in io.Reader) *CLI {
  function extractWinner (line 29) | func extractWinner(userInput string) string {

FILE: command-line/v3/CLI_test.go
  function TestCLI (line 10) | func TestCLI(t *testing.T) {
  type failOnEndReader (line 46) | type failOnEndReader struct
    method Read (line 51) | func (m failOnEndReader) Read(p []byte) (n int, err error) {

FILE: command-line/v3/cmd/cli/main.go
  constant dbFileName (line 11) | dbFileName = "game.db.json"
  function main (line 13) | func main() {

FILE: command-line/v3/cmd/webserver/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: command-line/v3/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 78) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 86) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 98) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function FileSystemPlayerStoreFromFile (line 39) | func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore,...
  function initialisePlayerDBFile (line 60) | func initialisePlayerDBFile(file *os.File) error {

FILE: command-line/v3/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 26) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 111) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 118) | func assertNoError(t testing.TB, err error) {

FILE: command-line/v3/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: command-line/v3/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: command-line/v3/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: command-line/v3/server_test.go
  function TestGETPlayers (line 12) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 53) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 74) | func TestLeague(t *testing.T) {
  function assertContentType (line 100) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 107) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 118) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 125) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 132) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 137) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 142) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 147) | func assertResponseBody(t testing.TB, got, want string) {

FILE: command-line/v3/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: command-line/v3/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: command-line/v3/testing.go
  type StubPlayerStore (line 6) | type StubPlayerStore struct
    method GetPlayerScore (line 13) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 19) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 24) | func (s *StubPlayerStore) GetLeague() League {
  function AssertPlayerWin (line 29) | func AssertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {

FILE: concurrency/v1/check_website.go
  function CheckWebsite (line 6) | func CheckWebsite(url string) bool {

FILE: concurrency/v1/check_websites.go
  type WebsiteChecker (line 4) | type WebsiteChecker
  function CheckWebsites (line 8) | func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {

FILE: concurrency/v1/check_websites_benchmark_test.go
  function slowStubWebsiteChecker (line 8) | func slowStubWebsiteChecker(_ string) bool {
  function BenchmarkCheckWebsites (line 13) | func BenchmarkCheckWebsites(b *testing.B) {

FILE: concurrency/v1/check_websites_test.go
  function mockWebsiteChecker (line 8) | func mockWebsiteChecker(url string) bool {
  function TestCheckWebsites (line 15) | func TestCheckWebsites(t *testing.T) {

FILE: concurrency/v2/check_website.go
  function CheckWebsite (line 6) | func CheckWebsite(url string) bool {

FILE: concurrency/v2/check_websites.go
  type WebsiteChecker (line 8) | type WebsiteChecker
  function CheckWebsites (line 12) | func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {

FILE: concurrency/v2/check_websites_benchmark_test.go
  function slowStubWebsiteChecker (line 8) | func slowStubWebsiteChecker(_ string) bool {
  function BenchmarkCheckWebsites (line 13) | func BenchmarkCheckWebsites(b *testing.B) {

FILE: concurrency/v2/check_websites_test.go
  function mockWebsiteChecker (line 8) | func mockWebsiteChecker(url string) bool {
  function TestCheckWebsites (line 15) | func TestCheckWebsites(t *testing.T) {

FILE: concurrency/v3/check_website.go
  function CheckWebsite (line 6) | func CheckWebsite(url string) bool {

FILE: concurrency/v3/check_websites.go
  type WebsiteChecker (line 4) | type WebsiteChecker
  type result (line 5) | type result struct
  function CheckWebsites (line 12) | func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {

FILE: concurrency/v3/check_websites_benchmark_test.go
  function slowStubWebsiteChecker (line 8) | func slowStubWebsiteChecker(_ string) bool {
  function BenchmarkCheckWebsites (line 13) | func BenchmarkCheckWebsites(b *testing.B) {

FILE: concurrency/v3/check_websites_test.go
  function mockWebsiteChecker (line 8) | func mockWebsiteChecker(url string) bool {
  function TestCheckWebsites (line 15) | func TestCheckWebsites(t *testing.T) {

FILE: context/v1/context.go
  type Store (line 9) | type Store interface
  function Server (line 14) | func Server(store Store) http.HandlerFunc {

FILE: context/v1/context_test.go
  type StubStore (line 9) | type StubStore struct
    method Fetch (line 13) | func (s *StubStore) Fetch() string {
  function TestServer (line 17) | func TestServer(t *testing.T) {

FILE: context/v2/context.go
  type Store (line 9) | type Store interface
  function Server (line 15) | func Server(store Store) http.HandlerFunc {

FILE: context/v2/context_test.go
  function TestServer (line 11) | func TestServer(t *testing.T) {

FILE: context/v2/testdoubles.go
  type SpyStore (line 9) | type SpyStore struct
    method Fetch (line 16) | func (s *SpyStore) Fetch() string {
    method Cancel (line 22) | func (s *SpyStore) Cancel() {
    method assertWasCancelled (line 26) | func (s *SpyStore) assertWasCancelled() {
    method assertWasNotCancelled (line 33) | func (s *SpyStore) assertWasNotCancelled() {

FILE: context/v3/context.go
  type Store (line 10) | type Store interface
  function Server (line 15) | func Server(store Store) http.HandlerFunc {

FILE: context/v3/context_test.go
  function TestServer (line 11) | func TestServer(t *testing.T) {

FILE: context/v3/testdoubles.go
  type SpyStore (line 12) | type SpyStore struct
    method Fetch (line 17) | func (s *SpyStore) Fetch(ctx context.Context) (string, error) {
  type SpyResponseWriter (line 44) | type SpyResponseWriter struct
    method Header (line 49) | func (s *SpyResponseWriter) Header() http.Header {
    method Write (line 55) | func (s *SpyResponseWriter) Write([]byte) (int, error) {
    method WriteHeader (line 61) | func (s *SpyResponseWriter) WriteHeader(statusCode int) {

FILE: di/v1/di.go
  function Greet (line 10) | func Greet(writer io.Writer, name string) {
  function main (line 14) | func main() {

FILE: di/v1/di_test.go
  function TestGreet (line 8) | func TestGreet(t *testing.T) {

FILE: di/v2/di.go
  function Greet (line 11) | func Greet(writer io.Writer, name string) {
  function MyGreeterHandler (line 16) | func MyGreeterHandler(w http.ResponseWriter, r *http.Request) {
  function main (line 20) | func main() {

FILE: di/v2/di_test.go
  function TestGreet (line 8) | func TestGreet(t *testing.T) {

FILE: for/v1/repeat.go
  function Repeat (line 4) | func Repeat(character string) string {

FILE: for/v1/repeat_test.go
  function TestRepeat (line 5) | func TestRepeat(t *testing.T) {

FILE: for/v2/repeat.go
  constant repeatCount (line 3) | repeatCount = 5
  function Repeat (line 6) | func Repeat(character string) string {

FILE: for/v2/repeat_test.go
  function TestRepeat (line 5) | func TestRepeat(t *testing.T) {

FILE: for/v3/repeat.go
  constant repeatCount (line 5) | repeatCount = 5
  function Repeat (line 8) | func Repeat(character string) string {

FILE: for/v3/repeat_test.go
  function TestRepeat (line 5) | func TestRepeat(t *testing.T) {

FILE: for/vx/repeat.go
  constant repeatCount (line 3) | repeatCount = 5
  function Repeat (line 6) | func Repeat(character string) string {

FILE: for/vx/repeat_test.go
  function TestRepeat (line 5) | func TestRepeat(t *testing.T) {
  function BenchmarkRepeat (line 14) | func BenchmarkRepeat(b *testing.B) {

FILE: generics/assert.go
  function AssertEqual (line 5) | func AssertEqual[T comparable](t *testing.T, got, want T) {
  function AssertNotEqual (line 12) | func AssertNotEqual[T comparable](t *testing.T, got, want T) {
  function AssertTrue (line 19) | func AssertTrue(t *testing.T, got bool) {
  function AssertFalse (line 26) | func AssertFalse(t *testing.T, got bool) {

FILE: generics/generics_test.go
  function TestAssertFunctions (line 5) | func TestAssertFunctions(t *testing.T) {
  function TestStack (line 19) | func TestStack(t *testing.T) {

FILE: generics/stack.go
  type Stack (line 3) | type Stack struct
  function NewStack (line 7) | func NewStack[T any]() *Stack[T] {
  method Push (line 11) | func (s *Stack[T]) Push(value T) {
  method IsEmpty (line 15) | func (s *Stack[T]) IsEmpty() bool {
  method Pop (line 19) | func (s *Stack[T]) Pop() (T, bool) {

FILE: hello-world/v1/hello.go
  function main (line 5) | func main() {

FILE: hello-world/v2/hello.go
  function Hello (line 6) | func Hello() string {
  function main (line 10) | func main() {

FILE: hello-world/v2/hello_test.go
  function TestHello (line 5) | func TestHello(t *testing.T) {

FILE: hello-world/v3/hello.go
  function Hello (line 6) | func Hello(name string) string {
  function main (line 10) | func main() {

FILE: hello-world/v3/hello_test.go
  function TestHello (line 5) | func TestHello(t *testing.T) {

FILE: hello-world/v4/hello.go
  constant englishHelloPrefix (line 5) | englishHelloPrefix = "Hello, "
  function Hello (line 8) | func Hello(name string) string {
  function main (line 12) | func main() {

FILE: hello-world/v4/hello_test.go
  function TestHello (line 5) | func TestHello(t *testing.T) {

FILE: hello-world/v5/hello.go
  constant englishHelloPrefix (line 5) | englishHelloPrefix = "Hello, "
  function Hello (line 8) | func Hello(name string) string {
  function main (line 15) | func main() {

FILE: hello-world/v5/hello_test.go
  function TestHello (line 5) | func TestHello(t *testing.T) {
  function assertCorrectMessage (line 20) | func assertCorrectMessage(t testing.TB, got, want string) {

FILE: hello-world/v6/hello.go
  constant spanish (line 5) | spanish = "Spanish"
  constant french (line 6) | french = "French"
  constant englishHelloPrefix (line 7) | englishHelloPrefix = "Hello, "
  constant spanishHelloPrefix (line 8) | spanishHelloPrefix = "Hola, "
  constant frenchHelloPrefix (line 9) | frenchHelloPrefix = "Bonjour, "
  function Hello (line 12) | func Hello(name string, language string) string {
  function main (line 28) | func main() {

FILE: hello-world/v6/hello_test.go
  function TestHello (line 5) | func TestHello(t *testing.T) {
  function assertCorrectMessage (line 32) | func assertCorrectMessage(t testing.TB, got, want string) {

FILE: hello-world/v7/hello.go
  constant spanish (line 5) | spanish = "Spanish"
  constant french (line 6) | french = "French"
  constant englishHelloPrefix (line 7) | englishHelloPrefix = "Hello, "
  constant spanishHelloPrefix (line 8) | spanishHelloPrefix = "Hola, "
  constant frenchHelloPrefix (line 9) | frenchHelloPrefix = "Bonjour, "
  function Hello (line 12) | func Hello(name string, language string) string {
  function main (line 29) | func main() {

FILE: hello-world/v7/hello_test.go
  function TestHello (line 5) | func TestHello(t *testing.T) {
  function assertCorrectMessage (line 31) | func assertCorrectMessage(t testing.TB, got, want string) {

FILE: hello-world/v8/hello.go
  constant spanish (line 5) | spanish = "Spanish"
  constant french (line 6) | french = "French"
  constant englishHelloPrefix (line 7) | englishHelloPrefix = "Hello, "
  constant spanishHelloPrefix (line 8) | spanishHelloPrefix = "Hola, "
  constant frenchHelloPrefix (line 9) | frenchHelloPrefix = "Bonjour, "
  function Hello (line 12) | func Hello(name string, language string) string {
  function greetingPrefix (line 20) | func greetingPrefix(language string) (prefix string) {
  function main (line 32) | func main() {

FILE: hello-world/v8/hello_test.go
  function TestHello (line 5) | func TestHello(t *testing.T) {
  function assertCorrectMessage (line 31) | func assertCorrectMessage(t testing.TB, got, want string) {

FILE: http-server/v1/main.go
  function main (line 8) | func main() {

FILE: http-server/v1/server.go
  function PlayerServer (line 9) | func PlayerServer(w http.ResponseWriter, r *http.Request) {

FILE: http-server/v1/server_test.go
  function TestGETPlayers (line 9) | func TestGETPlayers(t *testing.T) {

FILE: http-server/v2/main.go
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetPlayerScore (line 12) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
  function main (line 16) | func main() {

FILE: http-server/v2/server.go
  type PlayerStore (line 10) | type PlayerStore interface
  type PlayerServer (line 15) | type PlayerServer struct
    method ServeHTTP (line 19) | func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Reques...

FILE: http-server/v2/server_test.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 14) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
  function TestGETPlayers (line 19) | func TestGETPlayers(t *testing.T) {
  function assertStatus (line 66) | func assertStatus(t testing.TB, got, want int) {
  function newGetScoreRequest (line 73) | func newGetScoreRequest(name string) *http.Request {
  function assertResponseBody (line 78) | func assertResponseBody(t testing.TB, got, want string) {

FILE: http-server/v3/main.go
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetPlayerScore (line 12) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
  function main (line 16) | func main() {

FILE: http-server/v3/server.go
  type PlayerStore (line 10) | type PlayerStore interface
  type PlayerServer (line 15) | type PlayerServer struct
    method ServeHTTP (line 19) | func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Reques...
    method showScore (line 30) | func (p *PlayerServer) showScore(w http.ResponseWriter, r *http.Reques...
    method processWin (line 42) | func (p *PlayerServer) processWin(w http.ResponseWriter) {

FILE: http-server/v3/server_test.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 14) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
  function TestGETPlayers (line 19) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 66) | func TestStoreWins(t *testing.T) {
  function assertStatus (line 82) | func assertStatus(t testing.TB, got, want int) {
  function newGetScoreRequest (line 89) | func newGetScoreRequest(name string) *http.Request {
  function assertResponseBody (line 94) | func assertResponseBody(t testing.TB, got, want string) {

FILE: http-server/v4/main.go
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method RecordWin (line 12) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 16) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
  function main (line 20) | func main() {

FILE: http-server/v4/server.go
  type PlayerStore (line 10) | type PlayerStore interface
  type PlayerServer (line 16) | type PlayerServer struct
    method ServeHTTP (line 20) | func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Reques...
    method showScore (line 31) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 41) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {

FILE: http-server/v4/server_test.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 15) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 20) | func (s *StubPlayerStore) RecordWin(name string) {
  function TestGETPlayers (line 24) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function assertStatus (line 99) | func assertStatus(t testing.TB, got, want int) {
  function newGetScoreRequest (line 106) | func newGetScoreRequest(name string) *http.Request {
  function assertResponseBody (line 111) | func assertResponseBody(t testing.TB, got, want string) {

FILE: http-server/v5/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 6) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 14) | type InMemoryPlayerStore struct
    method RecordWin (line 21) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 28) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: http-server/v5/main.go
  function main (line 8) | func main() {

FILE: http-server/v5/server.go
  type PlayerStore (line 10) | type PlayerStore interface
  type PlayerServer (line 16) | type PlayerServer struct
    method ServeHTTP (line 20) | func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Reques...
    method showScore (line 30) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 40) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {

FILE: http-server/v5/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: http-server/v5/server_test.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 15) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 20) | func (s *StubPlayerStore) RecordWin(name string) {
  function TestGETPlayers (line 24) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function assertStatus (line 99) | func assertStatus(t testing.TB, got, want int) {
  function newGetScoreRequest (line 106) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 111) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 116) | func assertResponseBody(t testing.TB, got, want string) {

FILE: integers/v1/adder.go
  function Add (line 4) | func Add(x, y int) int {

FILE: integers/v1/adder_test.go
  function TestAdder (line 5) | func TestAdder(t *testing.T) {

FILE: integers/v2/adder.go
  function Add (line 4) | func Add(x, y int) int {

FILE: integers/v2/adder_test.go
  function TestAdder (line 8) | func TestAdder(t *testing.T) {
  function ExampleAdd (line 17) | func ExampleAdd() {

FILE: io/v1/file_system_store.go
  type FileSystemPlayerStore (line 8) | type FileSystemPlayerStore struct
    method GetLeague (line 13) | func (f *FileSystemPlayerStore) GetLeague() []Player {

FILE: io/v1/file_system_store_test.go
  function TestFileSystemStore (line 8) | func TestFileSystemStore(t *testing.T) {

FILE: io/v1/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetLeague (line 14) | func (i *InMemoryPlayerStore) GetLeague() []Player {
    method RecordWin (line 23) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 28) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: io/v1/league.go
  function NewLeague (line 9) | func NewLeague(rdr io.Reader) ([]Player, error) {

FILE: io/v1/main.go
  function main (line 8) | func main() {

FILE: io/v1/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v1/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v1/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() []Player {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v2/file_system_store.go
  type FileSystemPlayerStore (line 8) | type FileSystemPlayerStore struct
    method GetLeague (line 13) | func (f *FileSystemPlayerStore) GetLeague() []Player {
    method GetPlayerScore (line 20) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {

FILE: io/v2/file_system_store_test.go
  function TestFileSystemStore (line 8) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 44) | func assertScoreEquals(t testing.TB, got, want int) {

FILE: io/v2/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetLeague (line 14) | func (i *InMemoryPlayerStore) GetLeague() []Player {
    method RecordWin (line 23) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 28) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: io/v2/league.go
  function NewLeague (line 10) | func NewLeague(rdr io.Reader) ([]Player, error) {

FILE: io/v2/main.go
  function main (line 8) | func main() {

FILE: io/v2/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v2/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v2/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() []Player {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v3/file_system_store.go
  type FileSystemPlayerStore (line 9) | type FileSystemPlayerStore struct
    method GetLeague (line 14) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 21) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 33) | func (f *FileSystemPlayerStore) RecordWin(name string) {

FILE: io/v3/file_system_store_test.go
  function createTempFile (line 9) | func createTempFile(t testing.TB, initialData string) (io.ReadWriteSeeke...
  function TestFileSystemStore (line 28) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 81) | func assertScoreEquals(t testing.TB, got, want int) {

FILE: io/v3/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetLeague (line 14) | func (i *InMemoryPlayerStore) GetLeague() League {
    method RecordWin (line 23) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 28) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: io/v3/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: io/v3/main.go
  function main (line 8) | func main() {

FILE: io/v3/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v3/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v3/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v4/file_system_store.go
  type FileSystemPlayerStore (line 9) | type FileSystemPlayerStore struct
    method GetLeague (line 14) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 21) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 33) | func (f *FileSystemPlayerStore) RecordWin(name string) {

FILE: io/v4/file_system_store_test.go
  function createTempFile (line 9) | func createTempFile(t testing.TB, initialData string) (io.ReadWriteSeeke...
  function TestFileSystemStore (line 28) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 96) | func assertScoreEquals(t testing.TB, got, want int) {

FILE: io/v4/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetLeague (line 14) | func (i *InMemoryPlayerStore) GetLeague() League {
    method RecordWin (line 23) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 28) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: io/v4/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: io/v4/main.go
  function main (line 8) | func main() {

FILE: io/v4/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v4/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v4/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v5/file_system_store.go
  type FileSystemPlayerStore (line 9) | type FileSystemPlayerStore struct
    method GetLeague (line 14) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 21) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 33) | func (f *FileSystemPlayerStore) RecordWin(name string) {

FILE: io/v5/file_system_store_test.go
  function createTempFile (line 9) | func createTempFile(t testing.TB, initialData string) (io.ReadWriteSeeke...
  function TestFileSystemStore (line 28) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 96) | func assertScoreEquals(t testing.TB, got, want int) {

FILE: io/v5/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: io/v5/main.go
  constant dbFileName (line 9) | dbFileName = "game.db.json"
  function main (line 11) | func main() {

FILE: io/v5/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v5/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v5/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v6/file_system_store.go
  type FileSystemPlayerStore (line 9) | type FileSystemPlayerStore struct
    method GetLeague (line 26) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 31) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 43) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 15) | func NewFileSystemPlayerStore(database io.ReadWriteSeeker) *FileSystemPl...

FILE: io/v6/file_system_store_test.go
  function createTempFile (line 9) | func createTempFile(t testing.TB, initialData string) (io.ReadWriteSeeke...
  function TestFileSystemStore (line 28) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 96) | func assertScoreEquals(t testing.TB, got, want int) {

FILE: io/v6/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: io/v6/main.go
  constant dbFileName (line 9) | dbFileName = "game.db.json"
  function main (line 11) | func main() {

FILE: io/v6/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v6/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v6/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v7/file_system_store.go
  type FileSystemPlayerStore (line 10) | type FileSystemPlayerStore struct
    method GetLeague (line 27) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 32) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 44) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 16) | func NewFileSystemPlayerStore(file *os.File) *FileSystemPlayerStore {

FILE: io/v7/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 27) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 95) | func assertScoreEquals(t testing.TB, got, want int) {

FILE: io/v7/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: io/v7/main.go
  constant dbFileName (line 9) | dbFileName = "game.db.json"
  function main (line 11) | func main() {

FILE: io/v7/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v7/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v7/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v7/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: io/v7/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: io/v8/file_system_store.go
  type FileSystemPlayerStore (line 11) | type FileSystemPlayerStore struct
    method GetLeague (line 55) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 60) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 72) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 17) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function initialisePlayerDBFile (line 37) | func initialisePlayerDBFile(file *os.File) error {

FILE: io/v8/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 27) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 112) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 119) | func assertNoError(t testing.TB, err error) {

FILE: io/v8/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: io/v8/main.go
  constant dbFileName (line 9) | dbFileName = "game.db.json"
  function main (line 11) | func main() {

FILE: io/v8/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v8/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v8/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v8/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: io/v8/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: io/v9/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 56) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 64) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 76) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function initialisePlayerDBFile (line 38) | func initialisePlayerDBFile(file *os.File) error {

FILE: io/v9/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 27) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 112) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 119) | func assertNoError(t testing.TB, err error) {

FILE: io/v9/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: io/v9/main.go
  constant dbFileName (line 9) | dbFileName = "game.db.json"
  function main (line 11) | func main() {

FILE: io/v9/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: io/v9/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: io/v9/server_test.go
  type StubPlayerStore (line 12) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 27) | func (s *StubPlayerStore) GetLeague() League {
  function TestGETPlayers (line 31) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 72) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 100) | func TestLeague(t *testing.T) {
  function assertContentType (line 126) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 133) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 144) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 151) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 158) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 163) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 168) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 173) | func assertResponseBody(t testing.TB, got, want string) {

FILE: io/v9/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: io/v9/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: iterators/iterators_test.go
  function Concatenate (line 9) | func Concatenate(seq iter.Seq[string]) string {
  function Values (line 18) | func Values[K, V any](seq iter.Seq2[K, V]) iter.Seq[V] {
  function TestConcatenate (line 29) | func TestConcatenate(t *testing.T) {

FILE: json/v1/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method RecordWin (line 14) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 19) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: json/v1/main.go
  function main (line 8) | func main() {

FILE: json/v1/server.go
  type PlayerStore (line 10) | type PlayerStore interface
  type PlayerServer (line 16) | type PlayerServer struct
    method ServeHTTP (line 20) | func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Reques...
    method showScore (line 31) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 41) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {

FILE: json/v1/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: json/v1/server_test.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 15) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 20) | func (s *StubPlayerStore) RecordWin(name string) {
  function TestGETPlayers (line 24) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 64) | func TestStoreWins(t *testing.T) {
  function assertStatus (line 91) | func assertStatus(t testing.TB, got, want int) {
  function newGetScoreRequest (line 98) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 103) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 108) | func assertResponseBody(t testing.TB, got, want string) {

FILE: json/v2/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method RecordWin (line 14) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 19) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: json/v2/main.go
  function main (line 8) | func main() {

FILE: json/v2/server.go
  type PlayerStore (line 10) | type PlayerStore interface
  type PlayerServer (line 16) | type PlayerServer struct
    method leagueHandler (line 36) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 40) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 51) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 61) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  function NewPlayerServer (line 22) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: json/v2/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: json/v2/server_test.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 15) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 20) | func (s *StubPlayerStore) RecordWin(name string) {
  function TestGETPlayers (line 24) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 64) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 91) | func TestLeague(t *testing.T) {
  function assertStatus (line 105) | func assertStatus(t testing.TB, got, want int) {
  function newGetScoreRequest (line 112) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 117) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 122) | func assertResponseBody(t testing.TB, got, want string) {

FILE: json/v3/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method RecordWin (line 14) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 19) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: json/v3/main.go
  function main (line 8) | func main() {

FILE: json/v3/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 17) | type Player struct
  type PlayerServer (line 23) | type PlayerServer struct
    method leagueHandler (line 43) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method getLeagueTable (line 48) | func (p *PlayerServer) getLeagueTable() []Player {
    method playersHandler (line 54) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 65) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 75) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  function NewPlayerServer (line 29) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: json/v3/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: json/v3/server_test.go
  type StubPlayerStore (line 11) | type StubPlayerStore struct
    method GetPlayerScore (line 16) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 21) | func (s *StubPlayerStore) RecordWin(name string) {
  function TestGETPlayers (line 25) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 65) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 92) | func TestLeague(t *testing.T) {
  function assertStatus (line 114) | func assertStatus(t testing.TB, got, want int) {
  function newGetScoreRequest (line 121) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 126) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 131) | func assertResponseBody(t testing.TB, got, want string) {

FILE: json/v4/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetLeague (line 14) | func (i *InMemoryPlayerStore) GetLeague() []Player {
    method RecordWin (line 19) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 24) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: json/v4/main.go
  function main (line 8) | func main() {

FILE: json/v4/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 44) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 49) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 60) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 70) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  function NewPlayerServer (line 30) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: json/v4/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: json/v4/server_test.go
  type StubPlayerStore (line 13) | type StubPlayerStore struct
    method GetPlayerScore (line 19) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 24) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 28) | func (s *StubPlayerStore) GetLeague() []Player {
  function TestGETPlayers (line 32) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 73) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 101) | func TestLeague(t *testing.T) {
  function getLeagueFromResponse (line 125) | func getLeagueFromResponse(t testing.TB, body io.Reader) (league []Playe...
  function assertLeague (line 136) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 143) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 150) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 155) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 160) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 165) | func assertResponseBody(t testing.TB, got, want string) {

FILE: json/v5/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetLeague (line 14) | func (i *InMemoryPlayerStore) GetLeague() []Player {
    method RecordWin (line 19) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 24) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: json/v5/main.go
  function main (line 8) | func main() {

FILE: json/v5/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: json/v5/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: json/v5/server_test.go
  type StubPlayerStore (line 13) | type StubPlayerStore struct
    method GetPlayerScore (line 19) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 24) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 28) | func (s *StubPlayerStore) GetLeague() []Player {
  function TestGETPlayers (line 32) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 73) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 101) | func TestLeague(t *testing.T) {
  function assertContentType (line 127) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 134) | func getLeagueFromResponse(t testing.TB, body io.Reader) (league []Playe...
  function assertLeague (line 145) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 152) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 159) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 164) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 169) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 174) | func assertResponseBody(t testing.TB, got, want string) {

FILE: json/v6/in_memory_player_store.go
  function NewInMemoryPlayerStore (line 4) | func NewInMemoryPlayerStore() *InMemoryPlayerStore {
  type InMemoryPlayerStore (line 9) | type InMemoryPlayerStore struct
    method GetLeague (line 14) | func (i *InMemoryPlayerStore) GetLeague() []Player {
    method RecordWin (line 23) | func (i *InMemoryPlayerStore) RecordWin(name string) {
    method GetPlayerScore (line 28) | func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {

FILE: json/v6/main.go
  function main (line 8) | func main() {

FILE: json/v6/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: json/v6/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: json/v6/server_test.go
  type StubPlayerStore (line 13) | type StubPlayerStore struct
    method GetPlayerScore (line 19) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 24) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 28) | func (s *StubPlayerStore) GetLeague() []Player {
  function TestGETPlayers (line 32) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 73) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 101) | func TestLeague(t *testing.T) {
  function assertContentType (line 127) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 134) | func getLeagueFromResponse(t testing.TB, body io.Reader) (league []Playe...
  function assertLeague (line 145) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 152) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 159) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 164) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 169) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 174) | func assertResponseBody(t testing.TB, got, want string) {

FILE: maps/v1/dictionary.go
  type Dictionary (line 4) | type Dictionary
    method Search (line 7) | func (d Dictionary) Search(word string) string {

FILE: maps/v1/dictionary_test.go
  function TestSearch (line 5) | func TestSearch(t *testing.T) {
  function assertStrings (line 14) | func assertStrings(t testing.TB, got, want string) {

FILE: maps/v2/dictionary.go
  type Dictionary (line 6) | type Dictionary
    method Search (line 12) | func (d Dictionary) Search(word string) (string, error) {

FILE: maps/v2/dictionary_test.go
  function TestSearch (line 7) | func TestSearch(t *testing.T) {
  function assertStrings (line 24) | func assertStrings(t testing.TB, got, want string) {
  function assertError (line 32) | func assertError(t testing.TB, got, want error) {

FILE: maps/v3/dictionary.go
  type Dictionary (line 6) | type Dictionary
    method Search (line 12) | func (d Dictionary) Search(word string) (string, error) {
    method Add (line 22) | func (d Dictionary) Add(word, definition string) {

FILE: maps/v3/dictionary_test.go
  function TestSearch (line 7) | func TestSearch(t *testing.T) {
  function TestAdd (line 24) | func TestAdd(t *testing.T) {
  function assertStrings (line 34) | func assertStrings(t testing.TB, got, want string) {
  function assertError (line 42) | func assertError(t testing.TB, got, want error) {
  function assertDefinition (line 50) | func assertDefinition(t testing.TB, dictionary Dictionary, word, definit...

FILE: maps/v4/dictionary.go
  constant ErrNotFound (line 5) | ErrNotFound = DictionaryErr("could not find the word you were looking for")
  constant ErrWordExists (line 8) | ErrWordExists = DictionaryErr("cannot add word because it already exists")
  type DictionaryErr (line 12) | type DictionaryErr
    method Error (line 14) | func (e DictionaryErr) Error() string {
  type Dictionary (line 19) | type Dictionary
    method Search (line 22) | func (d Dictionary) Search(word string) (string, error) {
    method Add (line 32) | func (d Dictionary) Add(word, definition string) error {

FILE: maps/v4/dictionary_test.go
  function TestSearch (line 7) | func TestSearch(t *testing.T) {
  function TestAdd (line 24) | func TestAdd(t *testing.T) {
  function assertStrings (line 47) | func assertStrings(t testing.TB, got, want string) {
  function assertError (line 55) | func assertError(t testing.TB, got, want error) {
  function assertDefinition (line 63) | func assertDefinition(t testing.TB, dictionary Dictionary, word, definit...

FILE: maps/v5/dictionary.go
  constant ErrNotFound (line 5) | ErrNotFound = DictionaryErr("could not find the word you were looking for")
  constant ErrWordExists (line 8) | ErrWordExists = DictionaryErr("cannot add word because it already exists")
  type DictionaryErr (line 12) | type DictionaryErr
    method Error (line 14) | func (e DictionaryErr) Error() string {
  type Dictionary (line 19) | type Dictionary
    method Search (line 22) | func (d Dictionary) Search(word string) (string, error) {
    method Add (line 32) | func (d Dictionary) Add(word, definition string) error {
    method Update (line 48) | func (d Dictionary) Update(word, definition string) {

FILE: maps/v5/dictionary_test.go
  function TestSearch (line 7) | func TestSearch(t *testing.T) {
  function TestAdd (line 24) | func TestAdd(t *testing.T) {
  function TestUpdate (line 47) | func TestUpdate(t *testing.T) {
  function assertStrings (line 58) | func assertStrings(t testing.TB, got, want string) {
  function assertError (line 66) | func assertError(t testing.TB, got, want error) {
  function assertDefinition (line 74) | func assertDefinition(t testing.TB, dictionary Dictionary, word, definit...

FILE: maps/v6/dictionary.go
  constant ErrNotFound (line 5) | ErrNotFound = DictionaryErr("could not find the word you were looking for")
  constant ErrWordExists (line 8) | ErrWordExists = DictionaryErr("cannot add word because it already exists")
  constant ErrWordDoesNotExist (line 11) | ErrWordDoesNotExist = DictionaryErr("cannot perform operation on word be...
  type DictionaryErr (line 15) | type DictionaryErr
    method Error (line 17) | func (e DictionaryErr) Error() string {
  type Dictionary (line 22) | type Dictionary
    method Search (line 25) | func (d Dictionary) Search(word string) (string, error) {
    method Add (line 35) | func (d Dictionary) Add(word, definition string) error {
    method Update (line 51) | func (d Dictionary) Update(word, definition string) error {

FILE: maps/v6/dictionary_test.go
  function TestSearch (line 7) | func TestSearch(t *testing.T) {
  function TestAdd (line 24) | func TestAdd(t *testing.T) {
  function TestUpdate (line 47) | func TestUpdate(t *testing.T) {
  function assertStrings (line 70) | func assertStrings(t testing.TB, got, want string) {
  function assertError (line 78) | func assertError(t testing.TB, got, want error) {
  function assertDefinition (line 86) | func assertDefinition(t testing.TB, dictionary Dictionary, word, definit...

FILE: maps/v7/dictionary.go
  constant ErrNotFound (line 5) | ErrNotFound = DictionaryErr("could not find the word you were looking for")
  constant ErrWordExists (line 8) | ErrWordExists = DictionaryErr("cannot add word because it already exists")
  constant ErrWordDoesNotExist (line 11) | ErrWordDoesNotExist = DictionaryErr("cannot perform operation on word be...
  type DictionaryErr (line 15) | type DictionaryErr
    method Error (line 17) | func (e DictionaryErr) Error() string {
  type Dictionary (line 22) | type Dictionary
    method Search (line 25) | func (d Dictionary) Search(word string) (string, error) {
    method Add (line 35) | func (d Dictionary) Add(word, definition string) error {
    method Update (line 51) | func (d Dictionary) Update(word, definition string) error {
    method Delete (line 67) | func (d Dictionary) Delete(word string) error {

FILE: maps/v7/dictionary_test.go
  function TestSearch (line 7) | func TestSearch(t *testing.T) {
  function TestAdd (line 24) | func TestAdd(t *testing.T) {
  function TestUpdate (line 47) | func TestUpdate(t *testing.T) {
  function TestDelete (line 70) | func TestDelete(t *testing.T) {
  function assertStrings (line 94) | func assertStrings(t testing.TB, got, want string) {
  function assertError (line 102) | func assertError(t testing.TB, got, want error) {
  function assertDefinition (line 110) | func assertDefinition(t testing.TB, dictionary Dictionary, word, definit...

FILE: math/v1/clockface/clockface.go
  type Point (line 6) | type Point struct
  function SecondHand (line 13) | func SecondHand(t time.Time) Point {

FILE: math/v1/clockface/clockface_test.go
  function TestSecondHandAtMidnight (line 10) | func TestSecondHandAtMidnight(t *testing.T) {

FILE: math/v10/clockface/clockface.go
  type Point (line 9) | type Point struct
  function secondsInRadians (line 14) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 18) | func secondHandPoint(t time.Time) Point {
  function minutesInRadians (line 22) | func minutesInRadians(t time.Time) float64 {
  function minuteHandPoint (line 27) | func minuteHandPoint(t time.Time) Point {
  function hoursInRadians (line 31) | func hoursInRadians(t time.Time) float64 {
  function angleToPoint (line 36) | func angleToPoint(angle float64) Point {

FILE: math/v10/clockface/clockface/main.go
  function main (line 10) | func main() {

FILE: math/v10/clockface/clockface_acceptance_test.go
  type SVG (line 12) | type SVG struct
  type Line (line 24) | type Line struct
  type Circle (line 31) | type Circle struct
  function TestSVGWriterSecondHand (line 37) | func TestSVGWriterSecondHand(t *testing.T) {
  function TestSVGWriterMinutedHand (line 67) | func TestSVGWriterMinutedHand(t *testing.T) {
  function TestSVGWriterHourHand (line 93) | func TestSVGWriterHourHand(t *testing.T) {
  function containsLine (line 119) | func containsLine(l Line, ls []Line) bool {
  function simpleTime (line 128) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 132) | func testName(t time.Time) string {

FILE: math/v10/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function TestMinutesInRadians (line 49) | func TestMinutesInRadians(t *testing.T) {
  function TestMinuteHandPoint (line 68) | func TestMinuteHandPoint(t *testing.T) {
  function TestHoursInRadians (line 87) | func TestHoursInRadians(t *testing.T) {
  function roughlyEqualFloat64 (line 108) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 113) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 118) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 122) | func testName(t time.Time) string {

FILE: math/v10/clockface/svgWriter.go
  constant secondHandLength (line 10) | secondHandLength = 90
  constant minuteHandLength (line 11) | minuteHandLength = 80
  constant clockCentreX (line 12) | clockCentreX     = 150
  constant clockCentreY (line 13) | clockCentreY     = 150
  function SVGWriter (line 17) | func SVGWriter(w io.Writer, t time.Time) {
  function secondHand (line 25) | func secondHand(w io.Writer, t time.Time) {
  function minuteHand (line 30) | func minuteHand(w io.Writer, t time.Time) {
  function makeHand (line 35) | func makeHand(p Point, length float64) Point {
  constant svgStart (line 41) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 49) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 51) | svgEnd = `</svg>`

FILE: math/v11/clockface/clockface.go
  type Point (line 9) | type Point struct
  function secondsInRadians (line 14) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 18) | func secondHandPoint(t time.Time) Point {
  function minutesInRadians (line 22) | func minutesInRadians(t time.Time) float64 {
  function minuteHandPoint (line 27) | func minuteHandPoint(t time.Time) Point {
  function hoursInRadians (line 31) | func hoursInRadians(t time.Time) float64 {
  function hourHandPoint (line 36) | func hourHandPoint(t time.Time) Point {
  function angleToPoint (line 40) | func angleToPoint(angle float64) Point {

FILE: math/v11/clockface/clockface/main.go
  function main (line 10) | func main() {

FILE: math/v11/clockface/clockface_acceptance_test.go
  type SVG (line 12) | type SVG struct
  type Line (line 24) | type Line struct
  type Circle (line 31) | type Circle struct
  function TestSVGWriterSecondHand (line 37) | func TestSVGWriterSecondHand(t *testing.T) {
  function TestSVGWriterMinutedHand (line 67) | func TestSVGWriterMinutedHand(t *testing.T) {
  function TestSVGWriterHourHand (line 93) | func TestSVGWriterHourHand(t *testing.T) {
  function containsLine (line 119) | func containsLine(l Line, ls []Line) bool {
  function simpleTime (line 128) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 132) | func testName(t time.Time) string {

FILE: math/v11/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function TestMinutesInRadians (line 49) | func TestMinutesInRadians(t *testing.T) {
  function TestMinuteHandPoint (line 68) | func TestMinuteHandPoint(t *testing.T) {
  function TestHoursInRadians (line 87) | func TestHoursInRadians(t *testing.T) {
  function TestHourHandPoint (line 108) | func TestHourHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 127) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 132) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 137) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 141) | func testName(t time.Time) string {

FILE: math/v11/clockface/svgWriter.go
  constant secondHandLength (line 10) | secondHandLength = 90
  constant minuteHandLength (line 11) | minuteHandLength = 80
  constant clockCentreX (line 12) | clockCentreX     = 150
  constant clockCentreY (line 13) | clockCentreY     = 150
  function SVGWriter (line 17) | func SVGWriter(w io.Writer, t time.Time) {
  function secondHand (line 25) | func secondHand(w io.Writer, t time.Time) {
  function minuteHand (line 30) | func minuteHand(w io.Writer, t time.Time) {
  function makeHand (line 35) | func makeHand(p Point, length float64) Point {
  constant svgStart (line 41) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 49) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 51) | svgEnd = `</svg>`

FILE: math/v12/clockface/clockface.go
  constant secondsInHalfClock (line 9) | secondsInHalfClock = 30
  constant secondsInClock (line 10) | secondsInClock     = 2 * secondsInHalfClock
  constant minutesInHalfClock (line 11) | minutesInHalfClock = 30
  constant minutesInClock (line 12) | minutesInClock     = 2 * minutesInHalfClock
  constant hoursInHalfClock (line 13) | hoursInHalfClock   = 6
  constant hoursInClock (line 14) | hoursInClock       = 2 * hoursInHalfClock
  type Point (line 18) | type Point struct
  function secondsInRadians (line 23) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 27) | func secondHandPoint(t time.Time) Point {
  function minutesInRadians (line 31) | func minutesInRadians(t time.Time) float64 {
  function minuteHandPoint (line 36) | func minuteHandPoint(t time.Time) Point {
  function hoursInRadians (line 40) | func hoursInRadians(t time.Time) float64 {
  function hourHandPoint (line 45) | func hourHandPoint(t time.Time) Point {
  function angleToPoint (line 49) | func angleToPoint(angle float64) Point {

FILE: math/v12/clockface/clockface/main.go
  function main (line 10) | func main() {

FILE: math/v12/clockface/clockface_acceptance_test.go
  type SVG (line 12) | type SVG struct
  type Line (line 24) | type Line struct
  type Circle (line 31) | type Circle struct
  function TestSVGWriterSecondHand (line 37) | func TestSVGWriterSecondHand(t *testing.T) {
  function TestSVGWriterMinutedHand (line 67) | func TestSVGWriterMinutedHand(t *testing.T) {
  function TestSVGWriterHourHand (line 93) | func TestSVGWriterHourHand(t *testing.T) {
  function containsLine (line 119) | func containsLine(l Line, ls []Line) bool {
  function simpleTime (line 128) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 132) | func testName(t time.Time) string {

FILE: math/v12/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function TestMinutesInRadians (line 49) | func TestMinutesInRadians(t *testing.T) {
  function TestMinuteHandPoint (line 68) | func TestMinuteHandPoint(t *testing.T) {
  function TestHoursInRadians (line 87) | func TestHoursInRadians(t *testing.T) {
  function TestHourHandPoint (line 108) | func TestHourHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 127) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 132) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 137) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 141) | func testName(t time.Time) string {

FILE: math/v12/clockface/svgWriter.go
  constant secondHandLength (line 10) | secondHandLength = 90
  constant minuteHandLength (line 11) | minuteHandLength = 80
  constant hourHandLength (line 12) | hourHandLength   = 50
  constant clockCentreX (line 13) | clockCentreX     = 150
  constant clockCentreY (line 14) | clockCentreY     = 150
  function SVGWriter (line 18) | func SVGWriter(w io.Writer, t time.Time) {
  function secondHand (line 27) | func secondHand(w io.Writer, t time.Time) {
  function minuteHand (line 32) | func minuteHand(w io.Writer, t time.Time) {
  function hourHand (line 37) | func hourHand(w io.Writer, t time.Time) {
  function makeHand (line 42) | func makeHand(p Point, length float64) Point {
  constant svgStart (line 48) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 56) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 58) | svgEnd = `</svg>`

FILE: math/v2/clockface/clockface.go
  type Point (line 9) | type Point struct
  function SecondHand (line 16) | func SecondHand(t time.Time) Point {
  function secondsInRadians (line 20) | func secondsInRadians(t time.Time) float64 {

FILE: math/v2/clockface/clockface_acceptance_test.go
  function TestSecondHandAtMidnight (line 10) | func TestSecondHandAtMidnight(t *testing.T) {

FILE: math/v2/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {

FILE: math/v3/clockface/clockface.go
  type Point (line 9) | type Point struct
  function SecondHand (line 16) | func SecondHand(t time.Time) Point {
  function secondsInRadians (line 20) | func secondsInRadians(t time.Time) float64 {

FILE: math/v3/clockface/clockface_acceptance_test.go
  function TestSecondHandAtMidnight (line 10) | func TestSecondHandAtMidnight(t *testing.T) {

FILE: math/v3/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function simpleTime (line 30) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 34) | func testName(t time.Time) string {

FILE: math/v4/clockface/clockface.go
  type Point (line 9) | type Point struct
  function SecondHand (line 16) | func SecondHand(t time.Time) Point {
  function secondsInRadians (line 20) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 24) | func secondHandPoint(t time.Time) Point {

FILE: math/v4/clockface/clockface_acceptance_test.go
  function TestSecondHandAtMidnight (line 10) | func TestSecondHandAtMidnight(t *testing.T) {

FILE: math/v4/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 49) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 54) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 59) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 63) | func testName(t time.Time) string {

FILE: math/v5/clockface/clockface.go
  type Point (line 9) | type Point struct
  constant secondHandLength (line 14) | secondHandLength = 90
  constant clockCentreX (line 15) | clockCentreX = 150
  constant clockCentreY (line 16) | clockCentreY = 150
  function SecondHand (line 20) | func SecondHand(t time.Time) Point {
  function secondsInRadians (line 28) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 32) | func secondHandPoint(t time.Time) Point {

FILE: math/v5/clockface/clockface_acceptance_test.go
  function TestSecondHandAtMidnight (line 10) | func TestSecondHandAtMidnight(t *testing.T) {
  function TestSecondHandAt30Seconds (line 21) | func TestSecondHandAt30Seconds(t *testing.T) {

FILE: math/v5/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 49) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 54) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 59) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 63) | func testName(t time.Time) string {

FILE: math/v6/clockface/clockface.go
  type Point (line 9) | type Point struct
  constant secondHandLength (line 14) | secondHandLength = 90
  constant clockCentreX (line 15) | clockCentreX = 150
  constant clockCentreY (line 16) | clockCentreY = 150
  function SecondHand (line 20) | func SecondHand(t time.Time) Point {
  function secondsInRadians (line 28) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 32) | func secondHandPoint(t time.Time) Point {

FILE: math/v6/clockface/clockface/main.go
  function main (line 12) | func main() {
  function secondHandTag (line 21) | func secondHandTag(p clockface.Point) string {
  constant svgStart (line 25) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 33) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 35) | svgEnd = `</svg>`

FILE: math/v6/clockface/clockface_acceptance_test.go
  function TestSecondHandAtMidnight (line 10) | func TestSecondHandAtMidnight(t *testing.T) {
  function TestSecondHandAt30Seconds (line 21) | func TestSecondHandAt30Seconds(t *testing.T) {

FILE: math/v6/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 49) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 54) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 59) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 63) | func testName(t time.Time) string {

FILE: math/v7/clockface/clockface.go
  type Point (line 9) | type Point struct
  function secondsInRadians (line 14) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 18) | func secondHandPoint(t time.Time) Point {

FILE: math/v7/clockface/clockface/main.go
  function main (line 10) | func main() {

FILE: math/v7/clockface/clockface_acceptance_test.go
  function TestSVGWriterAtMidnight (line 11) | func TestSVGWriterAtMidnight(t *testing.T) {
  function TestSVGWriterAt30Seconds (line 25) | func TestSVGWriterAt30Seconds(t *testing.T) {

FILE: math/v7/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 49) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 54) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 59) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 63) | func testName(t time.Time) string {

FILE: math/v7/clockface/svgWriter.go
  constant secondHandLength (line 10) | secondHandLength = 90
  constant clockCentreX (line 11) | clockCentreX     = 150
  constant clockCentreY (line 12) | clockCentreY     = 150
  function SVGWriter (line 16) | func SVGWriter(w io.Writer, t time.Time) {
  function secondHand (line 23) | func secondHand(w io.Writer, t time.Time) {
  constant svgStart (line 31) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 39) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 41) | svgEnd = `</svg>`

FILE: math/v7b/clockface/clockface.go
  type Point (line 9) | type Point struct
  function secondsInRadians (line 14) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 18) | func secondHandPoint(t time.Time) Point {

FILE: math/v7b/clockface/clockface/main.go
  function main (line 10) | func main() {

FILE: math/v7b/clockface/clockface_acceptance_test.go
  type Svg (line 12) | type Svg struct
  function TestSVGWriterAtMidnight (line 37) | func TestSVGWriterAtMidnight(t *testing.T) {

FILE: math/v7b/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 49) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 54) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 59) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 63) | func testName(t time.Time) string {

FILE: math/v7b/clockface/svgWriter.go
  constant secondHandLength (line 10) | secondHandLength = 90
  constant clockCentreX (line 11) | clockCentreX     = 150
  constant clockCentreY (line 12) | clockCentreY     = 150
  function SVGWriter (line 16) | func SVGWriter(w io.Writer, t time.Time) {
  function secondHand (line 23) | func secondHand(w io.Writer, t time.Time) {
  constant svgStart (line 31) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 39) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 41) | svgEnd = `</svg>`

FILE: math/v7c/clockface/clockface.go
  type Point (line 9) | type Point struct
  function secondsInRadians (line 14) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 18) | func secondHandPoint(t time.Time) Point {

FILE: math/v7c/clockface/clockface/main.go
  function main (line 10) | func main() {

FILE: math/v7c/clockface/clockface_acceptance_test.go
  type SVG (line 12) | type SVG struct
  type Line (line 24) | type Line struct
  type Circle (line 31) | type Circle struct
  function TestSVGWriterSecondHand (line 37) | func TestSVGWriterSecondHand(t *testing.T) {
  function containsLine (line 67) | func containsLine(l Line, ls []Line) bool {
  function simpleTime (line 76) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 80) | func testName(t time.Time) string {

FILE: math/v7c/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 49) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 54) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 59) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 63) | func testName(t time.Time) string {

FILE: math/v7c/clockface/svgWriter.go
  constant secondHandLength (line 10) | secondHandLength = 90
  constant clockCentreX (line 11) | clockCentreX     = 150
  constant clockCentreY (line 12) | clockCentreY     = 150
  function SVGWriter (line 16) | func SVGWriter(w io.Writer, t time.Time) {
  function secondHand (line 23) | func secondHand(w io.Writer, t time.Time) {
  constant svgStart (line 31) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 39) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 41) | svgEnd = `</svg>`

FILE: math/v8/clockface/clockface.go
  type Point (line 9) | type Point struct
  function secondsInRadians (line 14) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 18) | func secondHandPoint(t time.Time) Point {
  function minutesInRadians (line 26) | func minutesInRadians(t time.Time) float64 {

FILE: math/v8/clockface/clockface/main.go
  function main (line 10) | func main() {

FILE: math/v8/clockface/clockface_acceptance_test.go
  type SVG (line 12) | type SVG struct
  type Line (line 24) | type Line struct
  type Circle (line 31) | type Circle struct
  function TestSVGWriterSecondHand (line 37) | func TestSVGWriterSecondHand(t *testing.T) {
  function TestSVGWriterMinuteHand (line 67) | func TestSVGWriterMinuteHand(t *testing.T) {
  function containsLine (line 93) | func containsLine(l Line, ls []Line) bool {
  function simpleTime (line 102) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 106) | func testName(t time.Time) string {

FILE: math/v8/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function TestMinutesInRadians (line 49) | func TestMinutesInRadians(t *testing.T) {
  function roughlyEqualFloat64 (line 68) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 73) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 78) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 82) | func testName(t time.Time) string {

FILE: math/v8/clockface/svgWriter.go
  constant secondHandLength (line 10) | secondHandLength = 90
  constant clockCentreX (line 11) | clockCentreX     = 150
  constant clockCentreY (line 12) | clockCentreY     = 150
  function SVGWriter (line 16) | func SVGWriter(w io.Writer, t time.Time) {
  function secondHand (line 23) | func secondHand(w io.Writer, t time.Time) {
  constant svgStart (line 31) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 39) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 41) | svgEnd = `</svg>`

FILE: math/v9/clockface/clockface.go
  type Point (line 9) | type Point struct
  function secondsInRadians (line 14) | func secondsInRadians(t time.Time) float64 {
  function secondHandPoint (line 18) | func secondHandPoint(t time.Time) Point {
  function minutesInRadians (line 22) | func minutesInRadians(t time.Time) float64 {
  function minuteHandPoint (line 27) | func minuteHandPoint(t time.Time) Point {
  function angleToPoint (line 31) | func angleToPoint(angle float64) Point {

FILE: math/v9/clockface/clockface/main.go
  function main (line 10) | func main() {

FILE: math/v9/clockface/clockface_acceptance_test.go
  type SVG (line 12) | type SVG struct
  type Line (line 24) | type Line struct
  type Circle (line 31) | type Circle struct
  function TestSVGWriterSecondHand (line 37) | func TestSVGWriterSecondHand(t *testing.T) {
  function TestSVGWriterMinutedHand (line 67) | func TestSVGWriterMinutedHand(t *testing.T) {
  function containsLine (line 93) | func containsLine(l Line, ls []Line) bool {
  function simpleTime (line 102) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 106) | func testName(t time.Time) string {

FILE: math/v9/clockface/clockface_test.go
  function TestSecondsInRadians (line 9) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 30) | func TestSecondHandPoint(t *testing.T) {
  function TestMinutesInRadians (line 49) | func TestMinutesInRadians(t *testing.T) {
  function TestMinuteHandPoint (line 68) | func TestMinuteHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 87) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 92) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 97) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 101) | func testName(t time.Time) string {

FILE: math/v9/clockface/svgWriter.go
  constant secondHandLength (line 10) | secondHandLength = 90
  constant minuteHandLength (line 11) | minuteHandLength = 80
  constant clockCentreX (line 12) | clockCentreX     = 150
  constant clockCentreY (line 13) | clockCentreY     = 150
  function SVGWriter (line 17) | func SVGWriter(w io.Writer, t time.Time) {
  function secondHand (line 25) | func secondHand(w io.Writer, t time.Time) {
  function minuteHand (line 30) | func minuteHand(w io.Writer, t time.Time) {
  function makeHand (line 35) | func makeHand(p Point, length float64) Point {
  constant svgStart (line 41) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 49) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 51) | svgEnd = `</svg>`

FILE: math/vFinal/clockface/clockface.go
  constant secondsInHalfClock (line 11) | secondsInHalfClock = 30
  constant secondsInClock (line 12) | secondsInClock     = 2 * secondsInHalfClock
  constant minutesInHalfClock (line 13) | minutesInHalfClock = 30
  constant minutesInClock (line 14) | minutesInClock     = 2 * minutesInHalfClock
  constant hoursInHalfClock (line 15) | hoursInHalfClock   = 6
  constant hoursInClock (line 16) | hoursInClock       = 2 * hoursInHalfClock
  type Point (line 21) | type Point struct
  function SecondsInRadians (line 27) | func SecondsInRadians(t time.Time) float64 {
  function SecondHandPoint (line 33) | func SecondHandPoint(t time.Time) Point {
  function MinutesInRadians (line 38) | func MinutesInRadians(t time.Time) float64 {
  function MinuteHandPoint (line 45) | func MinuteHandPoint(t time.Time) Point {
  function HoursInRadians (line 50) | func HoursInRadians(t time.Time) float64 {
  function HourHandPoint (line 57) | func HourHandPoint(t time.Time) Point {
  function angleToPoint (line 61) | func angleToPoint(angle float64) Point {

FILE: math/vFinal/clockface/clockface/main.go
  function main (line 11) | func main() {

FILE: math/vFinal/clockface/clockface_test.go
  function TestSecondsInRadians (line 11) | func TestSecondsInRadians(t *testing.T) {
  function TestSecondHandPoint (line 32) | func TestSecondHandPoint(t *testing.T) {
  function TestMinutesInRadians (line 51) | func TestMinutesInRadians(t *testing.T) {
  function TestMinuteHandPoint (line 70) | func TestMinuteHandPoint(t *testing.T) {
  function TestHoursInRadians (line 89) | func TestHoursInRadians(t *testing.T) {
  function TestHourHandPoint (line 110) | func TestHourHandPoint(t *testing.T) {
  function roughlyEqualFloat64 (line 129) | func roughlyEqualFloat64(a, b float64) bool {
  function roughlyEqualPoint (line 134) | func roughlyEqualPoint(a, b Point) bool {
  function simpleTime (line 139) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 143) | func testName(t time.Time) string {

FILE: math/vFinal/clockface/svg/svg.go
  constant secondHandLength (line 13) | secondHandLength = 90
  constant minuteHandLength (line 14) | minuteHandLength = 80
  constant hourHandLength (line 15) | hourHandLength   = 50
  constant clockCentreX (line 16) | clockCentreX     = 150
  constant clockCentreY (line 17) | clockCentreY     = 150
  function Write (line 21) | func Write(w io.Writer, t time.Time) {
  function secondHand (line 30) | func secondHand(w io.Writer, t time.Time) {
  function minuteHand (line 35) | func minuteHand(w io.Writer, t time.Time) {
  function hourHand (line 40) | func hourHand(w io.Writer, t time.Time) {
  function makeHand (line 45) | func makeHand(p cf.Point, length float64) cf.Point {
  constant svgStart (line 51) | svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  constant bezel (line 59) | bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;...
  constant svgEnd (line 61) | svgEnd = `</svg>`

FILE: math/vFinal/clockface/svg/svg_test.go
  type SVG (line 12) | type SVG struct
  type Line (line 24) | type Line struct
  type Circle (line 31) | type Circle struct
  function TestSVGWriterSecondHand (line 37) | func TestSVGWriterSecondHand(t *testing.T) {
  function TestSVGWriterMinutedHand (line 67) | func TestSVGWriterMinutedHand(t *testing.T) {
  function TestSVGWriterHourHand (line 93) | func TestSVGWriterHourHand(t *testing.T) {
  function containsLine (line 119) | func containsLine(l Line, ls []Line) bool {
  function simpleTime (line 128) | func simpleTime(hours, minutes, seconds int) time.Time {
  function testName (line 132) | func testName(t time.Time) string {

FILE: mocking/v1/countdown_test.go
  function TestCountdown (line 8) | func TestCountdown(t *testing.T) {

FILE: mocking/v1/main.go
  function Countdown (line 10) | func Countdown(out io.Writer) {
  function main (line 14) | func main() {

FILE: mocking/v2/countdown_test.go
  function TestCountdown (line 8) | func TestCountdown(t *testing.T) {

FILE: mocking/v2/main.go
  constant finalWord (line 9) | finalWord = "Go!"
  constant countdownStart (line 10) | countdownStart = 3
  function Countdown (line 13) | func Countdown(out io.Writer) {
  function main (line 20) | func main() {

FILE: mocking/v3/countdown_test.go
  function TestCountdown (line 8) | func TestCountdown(t *testing.T) {
  type SpySleeper (line 29) | type SpySleeper struct
    method Sleep (line 33) | func (s *SpySleeper) Sleep() {

FILE: mocking/v3/main.go
  type Sleeper (line 11) | type Sleeper interface
  type DefaultSleeper (line 16) | type DefaultSleeper struct
    method Sleep (line 19) | func (d *DefaultSleeper) Sleep() {
  constant finalWord (line 23) | finalWord = "Go!"
  constant countdownStart (line 24) | countdownStart = 3
  function Countdown (line 27) | func Countdown(out io.Writer, sleeper Sleeper) {
  function main (line 36) | func main() {

FILE: mocking/v4/countdown_test.go
  function TestCountdown (line 9) | func TestCountdown(t *testing.T) {
  type SpyCountdownOperations (line 46) | type SpyCountdownOperations struct
    method Sleep (line 50) | func (s *SpyCountdownOperations) Sleep() {
    method Write (line 54) | func (s *SpyCountdownOperations) Write(p []byte) (n int, err error) {
  constant write (line 59) | write = "write"
  constant sleep (line 60) | sleep = "sleep"

FILE: mocking/v4/main.go
  type Sleeper (line 11) | type Sleeper interface
  type DefaultSleeper (line 16) | type DefaultSleeper struct
    method Sleep (line 19) | func (d *DefaultSleeper) Sleep() {
  constant finalWord (line 23) | finalWord = "Go!"
  constant countdownStart (line 24) | countdownStart = 3
  function Countdown (line 27) | func Countdown(out io.Writer, sleeper Sleeper) {
  function main (line 37) | func main() {

FILE: mocking/v5/countdown_test.go
  function TestCountdown (line 10) | func TestCountdown(t *testing.T) {
  function TestConfigurableSleeper (line 47) | func TestConfigurableSleeper(t *testing.T) {
  type SpyCountdownOperations (line 59) | type SpyCountdownOperations struct
    method Sleep (line 63) | func (s *SpyCountdownOperations) Sleep() {
    method Write (line 67) | func (s *SpyCountdownOperations) Write(p []byte) (n int, err error) {
  constant write (line 72) | write = "write"
  constant sleep (line 73) | sleep = "sleep"
  type SpyTime (line 75) | type SpyTime struct
    method SetDurationSlept (line 79) | func (s *SpyTime) SetDurationSlept(duration time.Duration) {

FILE: mocking/v5/main.go
  type Sleeper (line 11) | type Sleeper interface
  type ConfigurableSleeper (line 16) | type ConfigurableSleeper struct
    method Sleep (line 22) | func (c *ConfigurableSleeper) Sleep() {
  constant finalWord (line 26) | finalWord = "Go!"
  constant countdownStart (line 27) | countdownStart = 3
  function Countdown (line 30) | func Countdown(out io.Writer, sleeper Sleeper) {
  function main (line 40) | func main() {

FILE: mocking/v6/countdown_test.go
  function TestCountdown (line 10) | func TestCountdown(t *testing.T) {
  function TestConfigurableSleeper (line 47) | func TestConfigurableSleeper(t *testing.T) {
  type SpyCountdownOperations (line 59) | type SpyCountdownOperations struct
    method Sleep (line 63) | func (s *SpyCountdownOperations) Sleep() {
    method Write (line 67) | func (s *SpyCountdownOperations) Write(p []byte) (n int, err error) {
  constant write (line 72) | write = "write"
  constant sleep (line 73) | sleep = "sleep"
  type SpyTime (line 75) | type SpyTime struct
    method SetDurationSlept (line 79) | func (s *SpyTime) SetDurationSlept(duration time.Duration) {

FILE: mocking/v6/main.go
  type Sleeper (line 12) | type Sleeper interface
  type ConfigurableSleeper (line 17) | type ConfigurableSleeper struct
    method Sleep (line 23) | func (c *ConfigurableSleeper) Sleep() {
  constant finalWord (line 27) | finalWord = "Go!"
  function Countdown (line 30) | func Countdown(out io.Writer, sleeper Sleeper) {
  function countDownFrom (line 39) | func countDownFrom(from int) iter.Seq[int] {
  function main (line 49) | func main() {

FILE: pointers/v1/wallet.go
  type Bitcoin (line 6) | type Bitcoin
    method String (line 8) | func (b Bitcoin) String() string {
  type Wallet (line 13) | type Wallet struct
    method Deposit (line 18) | func (w *Wallet) Deposit(amount Bitcoin) {
    method Balance (line 23) | func (w *Wallet) Balance() Bitcoin {

FILE: pointers/v1/wallet_test.go
  function TestWallet (line 7) | func TestWallet(t *testing.T) {

FILE: pointers/v2/wallet.go
  type Bitcoin (line 6) | type Bitcoin
    method String (line 8) | func (b Bitcoin) String() string {
  type Wallet (line 13) | type Wallet struct
    method Deposit (line 18) | func (w *Wallet) Deposit(amount Bitcoin) {
    method Withdraw (line 23) | func (w *Wallet) Withdraw(amount Bitcoin) {
    method Balance (line 28) | func (w *Wallet) Balance() Bitcoin {

FILE: pointers/v2/wallet_test.go
  function TestWallet (line 7) | func TestWallet(t *testing.T) {

FILE: pointers/v3/wallet.go
  type Bitcoin (line 9) | type Bitcoin
    method String (line 11) | func (b Bitcoin) String() string {
  type Wallet (line 16) | type Wallet struct
    method Deposit (line 21) | func (w *Wallet) Deposit(amount Bitcoin) {
    method Withdraw (line 26) | func (w *Wallet) Withdraw(amount Bitcoin) error {
    method Balance (line 37) | func (w *Wallet) Balance() Bitcoin {

FILE: pointers/v3/wallet_test.go
  function TestWallet (line 7) | func TestWallet(t *testing.T) {

FILE: pointers/v4/wallet.go
  type Bitcoin (line 9) | type Bitcoin
    method String (line 11) | func (b Bitcoin) String() string {
  type Wallet (line 16) | type Wallet struct
    method Deposit (line 21) | func (w *Wallet) Deposit(amount Bitcoin) {
    method Withdraw (line 29) | func (w *Wallet) Withdraw(amount Bitcoin) error {
    method Balance (line 40) | func (w *Wallet) Balance() Bitcoin {

FILE: pointers/v4/wallet_test.go
  function TestWallet (line 7) | func TestWallet(t *testing.T) {
  function assertBalance (line 34) | func assertBalance(t testing.TB, wallet Wallet, want Bitcoin) {
  function assertNoError (line 43) | func assertNoError(t testing.TB, got error) {
  function assertError (line 50) | func assertError(t testing.TB, got, want error) {

FILE: q-and-a/context-aware-reader/context_aware_reader.go
  function NewCancellableReader (line 9) | func NewCancellableReader(ctx context.Context, rdr io.Reader) io.Reader {
  type readerCtx (line 16) | type readerCtx struct
    method Read (line 21) | func (r *readerCtx) Read(p []byte) (n int, err error) {

FILE: q-and-a/context-aware-reader/context_aware_reader_test.go
  function TestContextAwareReader (line 9) | func TestContextAwareReader(t *testing.T) {
  function assertBufferHas (line 56) | func assertBufferHas(t testing.TB, buf []byte, want string) {

FILE: q-and-a/error-types/error-types_test.go
  type BadStatusError (line 11) | type BadStatusError struct
    method Error (line 16) | func (b BadStatusError) Error() string {
  function DumbGetter (line 21) | func DumbGetter(url string) (string, error) {
  function TestDumbGetter (line 38) | func TestDumbGetter(t *testing.T) {

FILE: q-and-a/error-types/v2/error-types_test.go
  type BadStatusError (line 12) | type BadStatusError struct
    method Error (line 17) | func (b BadStatusError) Error() string {
  function DumbGetter (line 22) | func DumbGetter(url string) (string, error) {
  function TestDumbGetter (line 39) | func TestDumbGetter(t *testing.T) {

FILE: q-and-a/http-handlers-revisited/basic_test.go
  function Teapot (line 9) | func Teapot(res http.ResponseWriter, req *http.Request) {
  function TestTeapotHandler (line 13) | func TestTeapotHandler(t *testing.T) {

FILE: q-and-a/http-handlers-revisited/still_basic.go
  type User (line 11) | type User struct
  type UserService (line 16) | type UserService interface
  type UserServer (line 21) | type UserServer struct
    method RegisterUser (line 31) | func (u *UserServer) RegisterUser(w http.ResponseWriter, r *http.Reque...
  function NewUserServer (line 26) | func NewUserServer(service UserService) *UserServer {
  type MongoUserService (line 55) | type MongoUserService struct
    method Register (line 66) | func (m MongoUserService) Register(user User) (insertedID string, err ...
  function NewMongoUserService (line 59) | func NewMongoUserService() *MongoUserService {
  function main (line 71) | func main() {

FILE: q-and-a/http-handlers-revisited/still_basic_test.go
  type MockUserService (line 15) | type MockUserService struct
    method Register (line 20) | func (m *MockUserService) Register(user User) (insertedID string, err ...
  function TestRegisterUser (line 25) | func TestRegisterUser(t *testing.T) {
  function assertStatus (line 87) | func assertStatus(t testing.TB, got, want int) {
  function userToJSON (line 94) | func userToJSON(user User) io.Reader {

FILE: q-and-a/os-exec/os-exec_test.go
  type Payload (line 12) | type Payload struct
  function GetData (line 16) | func GetData(data io.Reader) string {
  function getXMLFromCommand (line 22) | func getXMLFromCommand() io.Reader {
  function TestGetDataIntegration (line 33) | func TestGetDataIntegration(t *testing.T) {
  function TestGetData (line 42) | func TestGetData(t *testing.T) {

FILE: reading-files/blogposts.go
  function NewPostsFromFS (line 8) | func NewPostsFromFS(fileSystem fs.FS) ([]Post, error) {
  function getPost (line 24) | func getPost(fileSystem fs.FS, f fs.DirEntry) (Post, error) {

FILE: reading-files/blogposts_test.go
  function TestNewBlogPosts (line 10) | func TestNewBlogPosts(t *testing.T) {
  function assertNoError (line 47) | func assertNoError(t *testing.T, err error) {
  function assertPostsLength (line 54) | func assertPostsLength(t *testing.T, posts []blogposts.Post, fs fstest.M...
  function assertPost (line 61) | func assertPost(t *testing.T, got blogposts.Post, want blogposts.Post) {

FILE: reading-files/post.go
  type Post (line 12) | type Post struct
  constant titleSeparator (line 20) | titleSeparator       = "Title: "
  constant descriptionSeparator (line 21) | descriptionSeparator = "Description: "
  constant tagsSeparator (line 22) | tagsSeparator        = "Tags: "
  function newPost (line 25) | func newPost(postBody io.Reader) (Post, error) {
  function readBody (line 41) | func readBody(scanner *bufio.Scanner) string {

FILE: reflection/v1/reflection.go
  function walk (line 5) | func walk(x interface{}, fn func(input string)) {

FILE: reflection/v1/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {

FILE: reflection/v10/reflection.go
  function walk (line 7) | func walk(x interface{}, fn func(input string)) {
  function getValue (line 41) | func getValue(x interface{}) reflect.Value {

FILE: reflection/v10/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {
  type Person (line 137) | type Person struct
  type Profile (line 142) | type Profile struct
  function assertContains (line 147) | func assertContains(t testing.TB, haystack []string, needle string) {

FILE: reflection/v2/reflection.go
  function walk (line 5) | func walk(x interface{}, fn func(input string)) {

FILE: reflection/v2/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {

FILE: reflection/v3/reflection.go
  function walk (line 7) | func walk(x interface{}, fn func(input string)) {

FILE: reflection/v3/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {

FILE: reflection/v4/reflection.go
  function walk (line 7) | func walk(x interface{}, fn func(input string)) {

FILE: reflection/v4/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {
  type Person (line 60) | type Person struct
  type Profile (line 65) | type Profile struct

FILE: reflection/v5/reflection.go
  function walk (line 7) | func walk(x interface{}, fn func(input string)) {
  function getValue (line 22) | func getValue(x interface{}) reflect.Value {

FILE: reflection/v5/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {
  type Person (line 68) | type Person struct
  type Profile (line 73) | type Profile struct

FILE: reflection/v6/reflection.go
  function walk (line 7) | func walk(x interface{}, fn func(input string)) {
  function getValue (line 29) | func getValue(x interface{}) reflect.Value {

FILE: reflection/v6/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {
  type Person (line 76) | type Person struct
  type Profile (line 81) | type Profile struct

FILE: reflection/v7/reflection.go
  function walk (line 7) | func walk(x interface{}, fn func(input string)) {
  function getValue (line 29) | func getValue(x interface{}) reflect.Value {

FILE: reflection/v7/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {
  type Person (line 84) | type Person struct
  type Profile (line 89) | type Profile struct

FILE: reflection/v8/reflection.go
  function walk (line 7) | func walk(x interface{}, fn func(input string)) {
  function getValue (line 32) | func getValue(x interface{}) reflect.Value {

FILE: reflection/v8/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {
  type Person (line 99) | type Person struct
  type Profile (line 104) | type Profile struct
  function assertContains (line 109) | func assertContains(t testing.TB, haystack []string, needle string) {

FILE: reflection/v9/reflection.go
  function walk (line 7) | func walk(x interface{}, fn func(input string)) {
  function getValue (line 36) | func getValue(x interface{}) reflect.Value {

FILE: reflection/v9/reflection_test.go
  function TestWalk (line 8) | func TestWalk(t *testing.T) {
  type Person (line 120) | type Person struct
  type Profile (line 125) | type Profile struct
  function assertContains (line 130) | func assertContains(t testing.TB, haystack []string, needle string) {

FILE: roman-numerals/v1/numeral_test.go
  function TestRomanNumerals (line 5) | func TestRomanNumerals(t *testing.T) {
  function ConvertToRoman (line 14) | func ConvertToRoman(arabic int) string {

FILE: roman-numerals/v10/numeral_test.go
  function TestConvertingToRomanNumerals (line 45) | func TestConvertingToRomanNumerals(t *testing.T) {
  function TestConvertingToArabic (line 56) | func TestConvertingToArabic(t *testing.T) {

FILE: roman-numerals/v10/roman_numerals.go
  function ConvertToArabic (line 6) | func ConvertToArabic(roman string) int {
  function ConvertToRoman (line 20) | func ConvertToRoman(arabic int) string {
  type romanNumeral (line 33) | type romanNumeral struct

FILE: roman-numerals/v11/numeral_test.go
  function TestConvertingToRomanNumerals (line 46) | func TestConvertingToRomanNumerals(t *testing.T) {
  function TestConvertingToArabic (line 57) | func TestConvertingToArabic(t *testing.T) {
  function TestPropertiesOfConversion (line 68) | func TestPropertiesOfConversion(t *testing.T) {

FILE: roman-numerals/v11/roman_numerals.go
  function ConvertToArabic (line 6) | func ConvertToArabic(roman string) uint16 {
  function ConvertToRoman (line 20) | func ConvertToRoman(arabic uint16) string {
  type romanNumeral (line 33) | type romanNumeral struct

FILE: roman-numerals/v2/numeral_test.go
  function TestRomanNumerals (line 5) | func TestRomanNumerals(t *testing.T) {
  function ConvertToRoman (line 25) | func ConvertToRoman(arabic int) string {

FILE: roman-numerals/v3/numeral_test.go
  function TestRomanNumerals (line 8) | func TestRomanNumerals(t *testing.T) {
  function ConvertToRoman (line 29) | func ConvertToRoman(arabic int) string {

FILE: roman-numerals/v4/numeral_test.go
  function TestRomanNumerals (line 8) | func TestRomanNumerals(t *testing.T) {
  function ConvertToRoman (line 30) | func ConvertToRoman(arabic int) string {

FILE: roman-numerals/v5/numeral_test.go
  function TestRomanNumerals (line 8) | func TestRomanNumerals(t *testing.T) {
  function ConvertToRoman (line 33) | func ConvertToRoman(arabic int) string {

FILE: roman-numerals/v6/numeral_test.go
  function TestRomanNumerals (line 8) | func TestRomanNumerals(t *testing.T) {
  function ConvertToRoman (line 34) | func ConvertToRoman(arabic int) string {

FILE: roman-numerals/v7/numeral_test.go
  function TestRomanNumerals (line 8) | func TestRomanNumerals(t *testing.T) {
  type RomanNumeral (line 40) | type RomanNumeral struct
  function ConvertToRoman (line 53) | func ConvertToRoman(arabic int) string {

FILE: roman-numerals/v8/numeral_test.go
  function TestRomanNumerals (line 8) | func TestRomanNumerals(t *testing.T) {
  type RomanNumeral (line 44) | type RomanNumeral struct
  function ConvertToRoman (line 59) | func ConvertToRoman(arabic int) string {

FILE: roman-numerals/v9/numeral_test.go
  function TestRomanNumerals (line 9) | func TestRomanNumerals(t *testing.T) {
  type RomanNumeral (line 54) | type RomanNumeral struct
  function ConvertToRoman (line 75) | func ConvertToRoman(arabic int) string {

FILE: select/v1/racer.go
  function Racer (line 9) | func Racer(a, b string) (winner string) {
  function measureResponseTime (line 20) | func measureResponseTime(url string) time.Duration {

FILE: select/v1/racer_test.go
  function TestRacer (line 10) | func TestRacer(t *testing.T) {
  function makeDelayedServer (line 29) | func makeDelayedServer(delay time.Duration) *httptest.Server {

FILE: select/v2/racer.go
  function Racer (line 8) | func Racer(a, b string) (winner string) {
  function ping (line 17) | func ping(url string) chan struct{} {

FILE: select/v2/racer_test.go
  function TestRacer (line 10) | func TestRacer(t *testing.T) {
  function makeDelayedServer (line 29) | func makeDelayedServer(delay time.Duration) *httptest.Server {

FILE: select/v3/racer.go
  function Racer (line 12) | func Racer(a, b string) (winner string, error error) {
  function ConfigurableRacer (line 17) | func ConfigurableRacer(a, b string, timeout time.Duration) (winner strin...
  function ping (line 28) | func ping(url string) chan struct{} {

FILE: select/v3/racer_test.go
  function TestRacer (line 10) | func TestRacer(t *testing.T) {
  function makeDelayedServer (line 47) | func makeDelayedServer(delay time.Duration) *httptest.Server {

FILE: structs/v1/shapes.go
  function Perimeter (line 4) | func Perimeter(width float64, height float64) float64 {

FILE: structs/v1/shapes_test.go
  function TestPerimeter (line 5) | func TestPerimeter(t *testing.T) {

FILE: structs/v2/shapes.go
  function Perimeter (line 4) | func Perimeter(width float64, height float64) float64 {
  function Area (line 9) | func Area(width float64, height float64) float64 {

FILE: structs/v2/shapes_test.go
  function TestPerimeter (line 5) | func TestPerimeter(t *testing.T) {
  function TestArea (line 14) | func TestArea(t *testing.T) {

FILE: structs/v3/shapes.go
  type Rectangle (line 4) | type Rectangle struct
  function Perimeter (line 10) | func Perimeter(rectangle Rectangle) float64 {
  function Area (line 15) | func Area(rectangle Rectangle) float64 {

FILE: structs/v3/shapes_test.go
  function TestPerimeter (line 5) | func TestPerimeter(t *testing.T) {
  function TestArea (line 15) | func TestArea(t *testing.T) {

FILE: structs/v4/shapes.go
  type Rectangle (line 6) | type Rectangle struct
    method Area (line 12) | func (r Rectangle) Area() float64 {
  function Perimeter (line 17) | func Perimeter(rectangle Rectangle) float64 {
  type Circle (line 22) | type Circle struct
    method Area (line 27) | func (c Circle) Area() float64 {

FILE: structs/v4/shapes_test.go
  function TestPerimeter (line 7) | func TestPerimeter(t *testing.T) {
  function TestArea (line 17) | func TestArea(t *testing.T) {

FILE: structs/v5/shapes.go
  type Shape (line 6) | type Shape interface
  type Rectangle (line 11) | type Rectangle struct
    method Area (line 17) | func (r Rectangle) Area() float64 {
  function Perimeter (line 22) | func Perimeter(rectangle Rectangle) float64 {
  type Circle (line 27) | type Circle struct
    method Area (line 32) | func (c Circle) Area() float64 {

FILE: structs/v5/shapes_test.go
  function TestPerimeter (line 7) | func TestPerimeter(t *testing.T) {
  function TestArea (line 17) | func TestArea(t *testing.T) {

FILE: structs/v6/shapes.go
  type Shape (line 6) | type Shape interface
  type Rectangle (line 11) | type Rectangle struct
    method Area (line 17) | func (r Rectangle) Area() float64 {
  function Perimeter (line 22) | func Perimeter(rectangle Rectangle) float64 {
  type Circle (line 27) | type Circle struct
    method Area (line 32) | func (c Circle) Area() float64 {

FILE: structs/v6/shapes_test.go
  function TestPerimeter (line 7) | func TestPerimeter(t *testing.T) {
  function TestArea (line 17) | func TestArea(t *testing.T) {

FILE: structs/v7/shapes.go
  type Shape (line 6) | type Shape interface
  type Rectangle (line 11) | type Rectangle struct
    method Area (line 17) | func (r Rectangle) Area() float64 {
  function Perimeter (line 22) | func Perimeter(rectangle Rectangle) float64 {
  type Circle (line 27) | type Circle struct
    method Area (line 32) | func (c Circle) Area() float64 {
  type Triangle (line 37) | type Triangle struct
    method Area (line 43) | func (c Triangle) Area() float64 {

FILE: structs/v7/shapes_test.go
  function TestPerimeter (line 7) | func TestPerimeter(t *testing.T) {
  function TestArea (line 17) | func TestArea(t *testing.T) {

FILE: structs/v8/shapes.go
  type Shape (line 6) | type Shape interface
  type Rectangle (line 11) | type Rectangle struct
    method Area (line 17) | func (r Rectangle) Area() float64 {
  function Perimeter (line 22) | func Perimeter(rectangle Rectangle) float64 {
  type Circle (line 27) | type Circle struct
    method Area (line 32) | func (c Circle) Area() float64 {
  type Triangle (line 37) | type Triangle struct
    method Area (line 43) | func (t Triangle) Area() float64 {

FILE: structs/v8/shapes_test.go
  function TestPerimeter (line 7) | func TestPerimeter(t *testing.T) {
  function TestArea (line 17) | func TestArea(t *testing.T) {

FILE: sync/v1/sync.go
  type Counter (line 4) | type Counter struct
    method Inc (line 9) | func (c *Counter) Inc() {
    method Value (line 14) | func (c *Counter) Value() int {

FILE: sync/v1/sync_test.go
  function TestCounter (line 7) | func TestCounter(t *testing.T) {
  function assertCounter (line 19) | func assertCounter(t testing.TB, got Counter, want int) {

FILE: sync/v2/sync.go
  type Counter (line 6) | type Counter struct
    method Inc (line 17) | func (c *Counter) Inc() {
    method Value (line 24) | func (c *Counter) Value() int {
  function NewCounter (line 12) | func NewCounter() *Counter {

FILE: sync/v2/sync_test.go
  function TestCounter (line 8) | func TestCounter(t *testing.T) {
  function assertCounter (line 39) | func assertCounter(t testing.TB, got *Counter, want int) {

FILE: time/v1/CLI.go
  type CLI (line 11) | type CLI struct
    method PlayPoker (line 27) | func (cli *CLI) PlayPoker() {
    method scheduleBlindAlerts (line 33) | func (cli *CLI) scheduleBlindAlerts() {
    method readLine (line 46) | func (cli *CLI) readLine() string {
  function NewCLI (line 18) | func NewCLI(store PlayerStore, in io.Reader, alerter BlindAlerter) *CLI {
  function extractWinner (line 42) | func extractWinner(userInput string) string {

FILE: time/v1/CLI_test.go
  type scheduledAlert (line 12) | type scheduledAlert struct
    method String (line 17) | func (s scheduledAlert) String() string {
  type SpyBlindAlerter (line 21) | type SpyBlindAlerter struct
    method ScheduleAlertAt (line 25) | func (s *SpyBlindAlerter) ScheduleAlertAt(at time.Duration, amount int) {
  function TestCLI (line 31) | func TestCLI(t *testing.T) {
  type failOnEndReader (line 102) | type failOnEndReader struct
    method Read (line 107) | func (m failOnEndReader) Read(p []byte) (n int, err error) {
  function assertScheduledAlert (line 118) | func assertScheduledAlert(t testing.TB, got, want scheduledAlert) {

FILE: time/v1/blind_alerter.go
  type BlindAlerter (line 10) | type BlindAlerter interface
  type BlindAlerterFunc (line 15) | type BlindAlerterFunc
    method ScheduleAlertAt (line 18) | func (a BlindAlerterFunc) ScheduleAlertAt(duration time.Duration, amou...
  function StdOutAlerter (line 23) | func StdOutAlerter(duration time.Duration, amount int) {

FILE: time/v1/cmd/cli/main.go
  constant dbFileName (line 11) | dbFileName = "game.db.json"
  function main (line 13) | func main() {

FILE: time/v1/cmd/webserver/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: time/v1/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 77) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 85) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 97) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function FileSystemPlayerStoreFromFile (line 39) | func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore,...
  function initialisePlayerDBFile (line 59) | func initialisePlayerDBFile(file *os.File) error {

FILE: time/v1/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 27) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 112) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 119) | func assertNoError(t testing.TB, err error) {

FILE: time/v1/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: time/v1/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: time/v1/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: time/v1/server_test.go
  function TestGETPlayers (line 12) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 53) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 74) | func TestLeague(t *testing.T) {
  function assertContentType (line 100) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 107) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 118) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 125) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 132) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 137) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 142) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 147) | func assertResponseBody(t testing.TB, got, want string) {

FILE: time/v1/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: time/v1/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: time/v1/testing.go
  type StubPlayerStore (line 6) | type StubPlayerStore struct
    method GetPlayerScore (line 13) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 19) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 24) | func (s *StubPlayerStore) GetLeague() League {
  function AssertPlayerWin (line 29) | func AssertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {

FILE: time/v2/CLI.go
  type Game (line 12) | type Game interface
  type CLI (line 18) | type CLI struct
    method PlayPoker (line 41) | func (cli *CLI) PlayPoker() {
    method readLine (line 58) | func (cli *CLI) readLine() string {
  function NewCLI (line 26) | func NewCLI(in io.Reader, out io.Writer, game Game) *CLI {
  constant PlayerPrompt (line 35) | PlayerPrompt = "Please enter the number of players: "
  constant ErrorPlayerNumberPrompt (line 38) | ErrorPlayerNumberPrompt = "ERROR: Please enter the number of players as ...
  function extractWinner (line 54) | func extractWinner(userInput string) string {

FILE: time/v2/CLI_test.go
  type GameSpy (line 17) | type GameSpy struct
    method Start (line 22) | func (g *GameSpy) Start(numberOfPlayers int) {
    method Finish (line 26) | func (g *GameSpy) Finish(winner string) {
  function TestCLI (line 30) | func TestCLI(t *testing.T) {
  type failOnEndReader (line 90) | type failOnEndReader struct
    method Read (line 95) | func (m failOnEndReader) Read(p []byte) (n int, err error) {
  function assertScheduledAlert (line 106) | func assertScheduledAlert(t testing.TB, got, want poker.ScheduledAlert) {

FILE: time/v2/blind_alerter.go
  type BlindAlerter (line 10) | type BlindAlerter interface
  type BlindAlerterFunc (line 15) | type BlindAlerterFunc
    method ScheduleAlertAt (line 18) | func (a BlindAlerterFunc) ScheduleAlertAt(duration time.Duration, amou...
  function StdOutAlerter (line 23) | func StdOutAlerter(duration time.Duration, amount int) {

FILE: time/v2/cmd/cli/main.go
  constant dbFileName (line 11) | dbFileName = "game.db.json"
  function main (line 13) | func main() {

FILE: time/v2/cmd/webserver/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: time/v2/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 77) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 85) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 97) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function FileSystemPlayerStoreFromFile (line 39) | func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore,...
  function initialisePlayerDBFile (line 59) | func initialisePlayerDBFile(file *os.File) error {

FILE: time/v2/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 27) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 112) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 119) | func assertNoError(t testing.TB, err error) {

FILE: time/v2/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: time/v2/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: time/v2/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: time/v2/server_test.go
  function TestGETPlayers (line 12) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 53) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 74) | func TestLeague(t *testing.T) {
  function assertContentType (line 100) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 107) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 118) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 125) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 132) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 137) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 142) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 147) | func assertResponseBody(t testing.TB, got, want string) {

FILE: time/v2/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: time/v2/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: time/v2/testing.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 17) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 28) | func (s *StubPlayerStore) GetLeague() League {
  function AssertPlayerWin (line 33) | func AssertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {
  type ScheduledAlert (line 46) | type ScheduledAlert struct
    method String (line 51) | func (s ScheduledAlert) String() string {
  type SpyBlindAlerter (line 56) | type SpyBlindAlerter struct
    method ScheduleAlertAt (line 61) | func (s *SpyBlindAlerter) ScheduleAlertAt(at time.Duration, amount int) {

FILE: time/v2/texas_holdem.go
  type TexasHoldem (line 6) | type TexasHoldem struct
    method Start (line 20) | func (p *TexasHoldem) Start(numberOfPlayers int) {
    method Finish (line 32) | func (p *TexasHoldem) Finish(winner string) {
  function NewTexasHoldem (line 12) | func NewTexasHoldem(alerter BlindAlerter, store PlayerStore) *TexasHoldem {

FILE: time/v2/texas_holdem_test.go
  function TestGame_Start (line 10) | func TestGame_Start(t *testing.T) {
  function TestGame_Finish (line 52) | func TestGame_Finish(t *testing.T) {
  function checkSchedulingCases (line 61) | func checkSchedulingCases(cases []poker.ScheduledAlert, t *testing.T, bl...

FILE: time/v3/BlindAlerter.go
  type BlindAlerter (line 10) | type BlindAlerter interface
  type BlindAlerterFunc (line 15) | type BlindAlerterFunc
    method ScheduleAlertAt (line 18) | func (a BlindAlerterFunc) ScheduleAlertAt(duration time.Duration, amou...
  function StdOutAlerter (line 23) | func StdOutAlerter(duration time.Duration, amount int) {

FILE: time/v3/CLI.go
  type CLI (line 13) | type CLI struct
    method PlayPoker (line 39) | func (cli *CLI) PlayPoker() {
    method readLine (line 69) | func (cli *CLI) readLine() string {
  function NewCLI (line 21) | func NewCLI(in io.Reader, out io.Writer, game Game) *CLI {
  constant PlayerPrompt (line 30) | PlayerPrompt = "Please enter the number of players: "
  constant BadPlayerInputErrMsg (line 33) | BadPlayerInputErrMsg = "Bad value received for number of players, please...
  constant BadWinnerInputMsg (line 36) | BadWinnerInputMsg = "invalid winner input, expect format of 'PlayerName ...
  function extractWinner (line 62) | func extractWinner(userInput string) (string, error) {

FILE: time/v3/CLI_test.go
  type GameSpy (line 17) | type GameSpy struct
    method Start (line 25) | func (g *GameSpy) Start(numberOfPlayers int) {
    method Finish (line 30) | func (g *GameSpy) Finish(winner string) {
  function userSends (line 35) | func userSends(messages ...string) io.Reader {
  function TestCLI (line 39) | func TestCLI(t *testing.T) {
  function assertGameStartedWith (line 94) | func assertGameStartedWith(t testing.TB, game *GameSpy, numberOfPlayersW...
  function assertGameNotFinished (line 101) | func assertGameNotFinished(t testing.TB, game *GameSpy) {
  function assertGameNotStarted (line 108) | func assertGameNotStarted(t testing.TB, game *GameSpy) {
  function assertFinishCalledWith (line 115) | func assertFinishCalledWith(t testing.TB, game *GameSpy, winner string) {
  function assertMessagesSentToUser (line 122) | func assertMessagesSentToUser(t testing.TB, stdout *bytes.Buffer, messag...
  function assertScheduledAlert (line 131) | func assertScheduledAlert(t testing.TB, got, want poker.ScheduledAlert) {

FILE: time/v3/cmd/cli/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: time/v3/cmd/webserver/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: time/v3/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 77) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 85) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 97) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function FileSystemPlayerStoreFromFile (line 39) | func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore,...
  function initialisePlayerDBFile (line 59) | func initialisePlayerDBFile(file *os.File) error {

FILE: time/v3/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 27) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 112) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 119) | func assertNoError(t testing.TB, err error) {

FILE: time/v3/game.go
  type Game (line 4) | type Game interface

FILE: time/v3/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: time/v3/server.go
  type PlayerStore (line 11) | type PlayerStore interface
  type Player (line 18) | type Player struct
  type PlayerServer (line 24) | type PlayerServer struct
    method leagueHandler (line 46) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 51) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 62) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 72) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 29) | jsonContentType = "application/json"
  function NewPlayerServer (line 32) | func NewPlayerServer(store PlayerStore) *PlayerServer {

FILE: time/v3/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: time/v3/server_test.go
  function TestGETPlayers (line 12) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 53) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 74) | func TestLeague(t *testing.T) {
  function assertContentType (line 100) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 107) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 118) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 125) | func assertStatus(t testing.TB, got, want int) {
  function newLeagueRequest (line 132) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 137) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 142) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 147) | func assertResponseBody(t testing.TB, got, want string) {

FILE: time/v3/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: time/v3/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: time/v3/testing.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 17) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 28) | func (s *StubPlayerStore) GetLeague() League {
  function AssertPlayerWin (line 33) | func AssertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {
  type ScheduledAlert (line 46) | type ScheduledAlert struct
    method String (line 51) | func (s ScheduledAlert) String() string {
  type SpyBlindAlerter (line 56) | type SpyBlindAlerter struct
    method ScheduleAlertAt (line 61) | func (s *SpyBlindAlerter) ScheduleAlertAt(at time.Duration, amount int) {

FILE: time/v3/texas_holdem.go
  type TexasHoldem (line 6) | type TexasHoldem struct
    method Start (line 20) | func (p *TexasHoldem) Start(numberOfPlayers int) {
    method Finish (line 32) | func (p *TexasHoldem) Finish(winner string) {
  function NewTexasHoldem (line 12) | func NewTexasHoldem(alerter BlindAlerter, store PlayerStore) *TexasHoldem {

FILE: time/v3/texas_holdem_test.go
  function TestGame_Start (line 10) | func TestGame_Start(t *testing.T) {
  function TestGame_Finish (line 52) | func TestGame_Finish(t *testing.T) {
  function checkSchedulingCases (line 61) | func checkSchedulingCases(cases []poker.ScheduledAlert, t *testing.T, bl...

FILE: todo/todo1_test.go
  type TodoList (line 5) | type TodoList struct
    method Pending (line 13) | func (t *TodoList) Pending() []string {
    method Put (line 17) | func (t *TodoList) Put(s string) {
    method MarkAsDone (line 21) | func (t *TodoList) MarkAsDone(s string) {
  function New (line 9) | func New() *TodoList {
  function TestToDo (line 31) | func TestToDo(t *testing.T) {
  function assertTodoLength (line 61) | func assertTodoLength(t testing.TB, list []string, want int) {
  function assertFirstTodoEquaL (line 69) | func assertFirstTodoEquaL(t testing.TB, todos []string, item string) {

FILE: websockets/v1/CLI.go
  type CLI (line 13) | type CLI struct
    method PlayPoker (line 39) | func (cli *CLI) PlayPoker() {
    method readLine (line 69) | func (cli *CLI) readLine() string {
  function NewCLI (line 21) | func NewCLI(in io.Reader, out io.Writer, game Game) *CLI {
  constant PlayerPrompt (line 30) | PlayerPrompt = "Please enter the number of players: "
  constant BadPlayerInputErrMsg (line 33) | BadPlayerInputErrMsg = "Bad value received for number of players, please...
  constant BadWinnerInputMsg (line 36) | BadWinnerInputMsg = "invalid winner input, expect format of 'PlayerName ...
  function extractWinner (line 62) | func extractWinner(userInput string) (string, error) {

FILE: websockets/v1/CLI_test.go
  type GameSpy (line 17) | type GameSpy struct
    method Start (line 25) | func (g *GameSpy) Start(numberOfPlayers int) {
    method Finish (line 30) | func (g *GameSpy) Finish(winner string) {
  function userSends (line 35) | func userSends(messages ...string) io.Reader {
  function TestCLI (line 39) | func TestCLI(t *testing.T) {
  function assertGameStartedWith (line 94) | func assertGameStartedWith(t testing.TB, game *GameSpy, numberOfPlayersW...
  function assertGameNotFinished (line 101) | func assertGameNotFinished(t testing.TB, game *GameSpy) {
  function assertGameNotStarted (line 108) | func assertGameNotStarted(t testing.TB, game *GameSpy) {
  function assertFinishCalledWith (line 115) | func assertFinishCalledWith(t testing.TB, game *GameSpy, winner string) {
  function assertMessagesSentToUser (line 122) | func assertMessagesSentToUser(t testing.TB, stdout *bytes.Buffer, messag...
  function assertScheduledAlert (line 131) | func assertScheduledAlert(t testing.TB, got, want poker.ScheduledAlert) {

FILE: websockets/v1/blind_alerter.go
  type BlindAlerter (line 10) | type BlindAlerter interface
  type BlindAlerterFunc (line 15) | type BlindAlerterFunc
    method ScheduleAlertAt (line 18) | func (a BlindAlerterFunc) ScheduleAlertAt(duration time.Duration, amou...
  function StdOutAlerter (line 23) | func StdOutAlerter(duration time.Duration, amount int) {

FILE: websockets/v1/cmd/cli/main.go
  constant dbFileName (line 11) | dbFileName = "game.db.json"
  function main (line 13) | func main() {

FILE: websockets/v1/cmd/webserver/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: websockets/v1/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 77) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 85) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 97) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function FileSystemPlayerStoreFromFile (line 39) | func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore,...
  function initialisePlayerDBFile (line 59) | func initialisePlayerDBFile(file *os.File) error {

FILE: websockets/v1/file_system_store_test.go
  function createTempFile (line 8) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 27) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 112) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 119) | func assertNoError(t testing.TB, err error) {

FILE: websockets/v1/game.go
  type Game (line 4) | type Game interface

FILE: websockets/v1/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: websockets/v1/server.go
  type PlayerStore (line 14) | type PlayerStore interface
  type Player (line 21) | type Player struct
  type PlayerServer (line 27) | type PlayerServer struct
    method webSocket (line 65) | func (p *PlayerServer) webSocket(w http.ResponseWriter, r *http.Reques...
    method game (line 71) | func (p *PlayerServer) game(w http.ResponseWriter, r *http.Request) {
    method leagueHandler (line 75) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 80) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 91) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 101) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 33) | jsonContentType = "application/json"
  constant htmlTemplatePath (line 34) | htmlTemplatePath = "game.html"
  function NewPlayerServer (line 37) | func NewPlayerServer(store PlayerStore) (*PlayerServer, error) {

FILE: websockets/v1/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 9) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: websockets/v1/server_test.go
  function mustMakePlayerServer (line 16) | func mustMakePlayerServer(t *testing.T, store PlayerStore) *PlayerServer {
  function TestGETPlayers (line 24) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 65) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 86) | func TestLeague(t *testing.T) {
  function TestGame (line 112) | func TestGame(t *testing.T) {
  function writeWSMessage (line 145) | func writeWSMessage(t testing.TB, conn *websocket.Conn, message string) {
  function assertContentType (line 152) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 159) | func getLeagueFromResponse(t testing.TB, body io.Reader) []Player {
  function assertLeague (line 170) | func assertLeague(t testing.TB, got, want []Player) {
  function assertStatus (line 177) | func assertStatus(t testing.TB, got *httptest.ResponseRecorder, want int) {
  function newGameRequest (line 184) | func newGameRequest() *http.Request {
  function newLeagueRequest (line 189) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 194) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 199) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 204) | func assertResponseBody(t testing.TB, got, want string) {

FILE: websockets/v1/tape.go
  type tape (line 8) | type tape struct
    method Write (line 12) | func (t *tape) Write(p []byte) (n int, err error) {

FILE: websockets/v1/tape_test.go
  function TestTape_Write (line 8) | func TestTape_Write(t *testing.T) {

FILE: websockets/v1/testing.go
  type StubPlayerStore (line 10) | type StubPlayerStore struct
    method GetPlayerScore (line 17) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 23) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 28) | func (s *StubPlayerStore) GetLeague() League {
  function AssertPlayerWin (line 33) | func AssertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {
  type ScheduledAlert (line 46) | type ScheduledAlert struct
    method String (line 51) | func (s ScheduledAlert) String() string {
  type SpyBlindAlerter (line 56) | type SpyBlindAlerter struct
    method ScheduleAlertAt (line 61) | func (s *SpyBlindAlerter) ScheduleAlertAt(at time.Duration, amount int) {

FILE: websockets/v1/texas_holdem.go
  type TexasHoldem (line 6) | type TexasHoldem struct
    method Start (line 20) | func (p *TexasHoldem) Start(numberOfPlayers int) {
    method Finish (line 32) | func (p *TexasHoldem) Finish(winner string) {
  function NewTexasHoldem (line 12) | func NewTexasHoldem(alerter BlindAlerter, store PlayerStore) *TexasHoldem {

FILE: websockets/v1/texas_holdem_test.go
  function TestGame_Start (line 10) | func TestGame_Start(t *testing.T) {
  function TestGame_Finish (line 52) | func TestGame_Finish(t *testing.T) {
  function checkSchedulingCases (line 61) | func checkSchedulingCases(cases []poker.ScheduledAlert, t *testing.T, bl...

FILE: websockets/v1/vendor/github.com/gorilla/websocket/client.go
  function NewClient (line 39) | func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, ...
  type Dialer (line 51) | type Dialer struct
    method Dial (line 105) | func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn...
    method DialContext (line 149) | func (d *Dialer) DialContext(ctx context.Context, urlStr string, reque...
  function hostPortNoPort (line 111) | func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
  function doHandshake (line 385) | func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/client_clone.go
  function cloneTLSConfig (line 11) | func cloneTLSConfig(cfg *tls.Config) *tls.Config {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/client_clone_legacy.go
  function cloneTLSConfig (line 15) | func cloneTLSConfig(cfg *tls.Config) *tls.Config {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/compression.go
  constant minCompressionLevel (line 16) | minCompressionLevel     = -2
  constant maxCompressionLevel (line 17) | maxCompressionLevel     = flate.BestCompression
  constant defaultCompressionLevel (line 18) | defaultCompressionLevel = 1
  function decompressNoContextTakeover (line 28) | func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
  function isValidCompressionLevel (line 40) | func isValidCompressionLevel(level int) bool {
  function compressNoContextTakeover (line 44) | func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteClos...
  type truncWriter (line 58) | type truncWriter struct
    method Write (line 64) | func (w *truncWriter) Write(p []byte) (int, error) {
  type flateWriteWrapper (line 92) | type flateWriteWrapper struct
    method Write (line 98) | func (w *flateWriteWrapper) Write(p []byte) (int, error) {
    method Close (line 105) | func (w *flateWriteWrapper) Close() error {
  type flateReadWrapper (line 122) | type flateReadWrapper struct
    method Read (line 126) | func (r *flateReadWrapper) Read(p []byte) (int, error) {
    method Close (line 140) | func (r *flateReadWrapper) Close() error {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/conn.go
  constant finalBit (line 23) | finalBit = 1 << 7
  constant rsv1Bit (line 24) | rsv1Bit  = 1 << 6
  constant rsv2Bit (line 25) | rsv2Bit  = 1 << 5
  constant rsv3Bit (line 26) | rsv3Bit  = 1 << 4
  constant maskBit (line 29) | maskBit = 1 << 7
  constant maxFrameHeaderSize (line 31) | maxFrameHeaderSize         = 2 + 8 + 4
  constant maxControlFramePayloadSize (line 32) | maxControlFramePayloadSize = 125
  constant writeWait (line 34) | writeWait = time.Second
  constant defaultReadBufferSize (line 36) | defaultReadBufferSize  = 4096
  constant defaultWriteBufferSize (line 37) | defaultWriteBufferSize = 4096
  constant continuationFrame (line 39) | continuationFrame = 0
  constant noFrame (line 40) | noFrame           = -1
  constant CloseNormalClosure (line 45) | CloseNormalClosure           = 1000
  constant CloseGoingAway (line 46) | CloseGoingAway               = 1001
  constant CloseProtocolError (line 47) | CloseProtocolError           = 1002
  constant CloseUnsupportedData (line 48) | CloseUnsupportedData         = 1003
  constant CloseNoStatusReceived (line 49) | CloseNoStatusReceived        = 1005
  constant CloseAbnormalClosure (line 50) | CloseAbnormalClosure         = 1006
  constant CloseInvalidFramePayloadData (line 51) | CloseInvalidFramePayloadData = 1007
  constant ClosePolicyViolation (line 52) | ClosePolicyViolation         = 1008
  constant CloseMessageTooBig (line 53) | CloseMessageTooBig           = 1009
  constant CloseMandatoryExtension (line 54) | CloseMandatoryExtension      = 1010
  constant CloseInternalServerErr (line 55) | CloseInternalServerErr       = 1011
  constant CloseServiceRestart (line 56) | CloseServiceRestart          = 1012
  constant CloseTryAgainLater (line 57) | CloseTryAgainLater           = 1013
  constant CloseTLSHandshake (line 58) | CloseTLSHandshake            = 1015
  constant TextMessage (line 65) | TextMessage = 1
  constant BinaryMessage (line 68) | BinaryMessage = 2
  constant CloseMessage (line 73) | CloseMessage = 8
  constant PingMessage (line 77) | PingMessage = 9
  constant PongMessage (line 81) | PongMessage = 10
  type netError (line 93) | type netError struct
    method Error (line 99) | func (e *netError) Error() string   { return e.msg }
    method Temporary (line 100) | func (e *netError) Temporary() bool { return e.temporary }
    method Timeout (line 101) | func (e *netError) Timeout() bool   { return e.timeout }
  type CloseError (line 104) | type CloseError struct
    method Error (line 112) | func (e *CloseError) Error() string {
  function IsCloseError (line 150) | func IsCloseError(err error, codes ...int) bool {
  function IsUnexpectedCloseError (line 163) | func IsUnexpectedCloseError(err error, expectedCodes ...int) bool {
  function newMaskKey (line 183) | func newMaskKey() [4]byte {
  function hideTempErr (line 188) | func hideTempErr(err error) error {
  function isControl (line 195) | func isControl(frameType int) bool {
  function isData (line 199) | func isData(frameType int) bool {
  function isValidReceivedCloseCode (line 222) | func isValidReceivedCloseCode(code int) bool {
  type BufferPool (line 228) | type BufferPool interface
  type writePoolData (line 238) | type writePoolData struct
  type Conn (line 241) | type Conn struct
    method Subprotocol (line 324) | func (c *Conn) Subprotocol() string {
    method Close (line 330) | func (c *Conn) Close() error {
    method LocalAddr (line 335) | func (c *Conn) LocalAddr() net.Addr {
    method RemoteAddr (line 340) | func (c *Conn) RemoteAddr() net.Addr {
    method writeFatal (line 346) | func (c *Conn) writeFatal(err error) error {
    method read (line 356) | func (c *Conn) read(n int) ([]byte, error) {
    method write (line 365) | func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []b...
    method WriteControl (line 393) | func (c *Conn) WriteControl(messageType int, data []byte, deadline tim...
    method prepWrite (line 454) | func (c *Conn) prepWrite(messageType int) error {
    method NextWriter (line 493) | func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
    method WritePreparedMessage (line 717) | func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error {
    method WriteMessage (line 740) | func (c *Conn) WriteMessage(messageType int, data []byte) error {
    method SetWriteDeadline (line 769) | func (c *Conn) SetWriteDeadline(t time.Time) error {
    method advanceFrame (line 776) | func (c *Conn) advanceFrame() (int, error) {
    method handleProtocolError (line 921) | func (c *Conn) handleProtocolError(message string) error {
    method NextReader (line 936) | func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
    method ReadMessage (line 1026) | func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
    method SetReadDeadline (line 1040) | func (c *Conn) SetReadDeadline(t time.Time) error {
    method SetReadLimit (line 1047) | func (c *Conn) SetReadLimit(limit int64) {
    method CloseHandler (line 1052) | func (c *Conn) CloseHandler() func(code int, text string) error {
    method SetCloseHandler (line 1070) | func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
    method PingHandler (line 1082) | func (c *Conn) PingHandler() func(appData string) error {
    method SetPingHandler (line 1093) | func (c *Conn) SetPingHandler(h func(appData string) error) {
    method PongHandler (line 1109) | func (c *Conn) PongHandler() func(appData string) error {
    method SetPongHandler (line 1120) | func (c *Conn) SetPongHandler(h func(appData string) error) {
    method UnderlyingConn (line 1129) | func (c *Conn) UnderlyingConn() net.Conn {
    method EnableWriteCompression (line 1136) | func (c *Conn) EnableWriteCompression(enable bool) {
    method SetCompressionLevel (line 1144) | func (c *Conn) SetCompressionLevel(level int) error {
  function newConn (line 282) | func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSi...
  type messageWriter (line 512) | type messageWriter struct
    method fatal (line 520) | func (w *messageWriter) fatal(err error) error {
    method flushFrame (line 530) | func (w *messageWriter) flushFrame(final bool, extra []byte) error {
    method ncopy (line 621) | func (w *messageWriter) ncopy(max int) (int, error) {
    method Write (line 635) | func (w *messageWriter) Write(p []byte) (int, error) {
    method WriteString (line 662) | func (w *messageWriter) WriteString(p string) (int, error) {
    method ReadFrom (line 680) | func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
    method Close (line 705) | func (w *messageWriter) Close() error {
  type messageReader (line 973) | type messageReader struct
    method Read (line 975) | func (r *messageReader) Read(b []byte) (int, error) {
    method Close (line 1020) | func (r *messageReader) Close() error {
  function FormatCloseMessage (line 1154) | func FormatCloseMessage(closeCode int, text string) []byte {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/conn_write.go
  method writeBufs (line 11) | func (c *Conn) writeBufs(bufs ...[]byte) error {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/conn_write_legacy.go
  method writeBufs (line 9) | func (c *Conn) writeBufs(bufs ...[]byte) error {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/json.go
  function WriteJSON (line 15) | func WriteJSON(c *Conn, v interface{}) error {
  method WriteJSON (line 23) | func (c *Conn) WriteJSON(v interface{}) error {
  function ReadJSON (line 40) | func ReadJSON(c *Conn, v interface{}) error {
  method ReadJSON (line 49) | func (c *Conn) ReadJSON(v interface{}) error {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/mask.go
  constant wordSize (line 11) | wordSize = int(unsafe.Sizeof(uintptr(0)))
  function maskBytes (line 13) | func maskBytes(key [4]byte, pos int, b []byte) int {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/mask_safe.go
  function maskBytes (line 9) | func maskBytes(key [4]byte, pos int, b []byte) int {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/prepared.go
  type PreparedMessage (line 19) | type PreparedMessage struct
    method frame (line 62) | func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
  type prepareKey (line 27) | type prepareKey struct
  type preparedFrame (line 34) | type preparedFrame struct
  function NewPreparedMessage (line 43) | func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage,...
  type prepareConn (line 96) | type prepareConn struct
    method Write (line 101) | func (pc *prepareConn) Write(p []byte) (int, error)        { return pc...
    method SetWriteDeadline (line 102) | func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }

FILE: websockets/v1/vendor/github.com/gorilla/websocket/proxy.go
  type netDialerFunc (line 17) | type netDialerFunc
    method Dial (line 19) | func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
  function init (line 23) | func init() {
  type httpProxyDialer (line 29) | type httpProxyDialer struct
    method Dial (line 34) | func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Con...

FILE: websockets/v1/vendor/github.com/gorilla/websocket/server.go
  type HandshakeError (line 18) | type HandshakeError struct
    method Error (line 22) | func (e HandshakeError) Error() string { return e.message }
  type Upgrader (line 26) | type Upgrader struct
    method returnError (line 75) | func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request,...
    method selectSubprotocol (line 99) | func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader h...
    method Upgrade (line 123) | func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, res...
  function checkSameOrigin (line 87) | func checkSameOrigin(r *http.Request) bool {
  function Upgrade (line 295) | func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http...
  function Subprotocols (line 309) | func Subprotocols(r *http.Request) []string {
  function IsWebSocketUpgrade (line 323) | func IsWebSocketUpgrade(r *http.Request) bool {
  function bufioReaderSize (line 329) | func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int {
  type writeHook (line 342) | type writeHook struct
    method Write (line 346) | func (wh *writeHook) Write(p []byte) (int, error) {
  function bufioWriterBuffer (line 352) | func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/trace.go
  function doHandshakeWithTrace (line 10) | func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Con...

FILE: websockets/v1/vendor/github.com/gorilla/websocket/trace_17.go
  function doHandshakeWithTrace (line 10) | func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Con...

FILE: websockets/v1/vendor/github.com/gorilla/websocket/util.go
  function computeAcceptKey (line 19) | func computeAcceptKey(challengeKey string) string {
  function generateChallengeKey (line 26) | func generateChallengeKey() (string, error) {
  constant isTokenOctet (line 38) | isTokenOctet = 1 << iota
  constant isSpaceOctet (line 39) | isSpaceOctet
  function init (line 42) | func init() {
  function skipSpace (line 76) | func skipSpace(s string) (rest string) {
  function nextToken (line 86) | func nextToken(s string) (token, rest string) {
  function nextTokenOrQuoted (line 96) | func nextTokenOrQuoted(s string) (value string, rest string) {
  function equalASCIIFold (line 132) | func equalASCIIFold(s, t string) bool {
  function tokenListContainsValue (line 156) | func tokenListContainsValue(header http.Header, name string, value strin...
  function parseExtensions (line 182) | func parseExtensions(header http.Header) []map[string]string {

FILE: websockets/v1/vendor/github.com/gorilla/websocket/x_net_proxy.go
  type proxy_direct (line 21) | type proxy_direct struct
    method Dial (line 26) | func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
  type proxy_PerHost (line 32) | type proxy_PerHost struct
    method Dial (line 53) | func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err er...
    method dialerForRequest (line 62) | func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
    method AddFromString (line 100) | func (p *proxy_PerHost) AddFromString(s string) {
    method AddIP (line 129) | func (p *proxy_PerHost) AddIP(ip net.IP) {
    method AddNetwork (line 136) | func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
    method AddZone (line 142) | func (p *proxy_PerHost) AddZone(zone string) {
    method AddHost (line 153) | func (p *proxy_PerHost) AddHost(host string) {
  function proxy_NewPerHost (line 44) | func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
  type proxy_Dialer (line 161) | type proxy_Dialer interface
  type proxy_Auth (line 167) | type proxy_Auth struct
  function proxy_FromEnvironment (line 173) | func proxy_FromEnvironment() proxy_Dialer {
  function proxy_RegisterDialerType (line 205) | func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dial...
  function proxy_FromURL (line 214) | func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, erro...
  type proxy_envOnce (line 253) | type proxy_envOnce struct
    method Get (line 259) | func (e *proxy_envOnce) Get() string {
    method init (line 264) | func (e *proxy_envOnce) init() {
  function proxy_SOCKS5 (line 275) | func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_...
  type proxy_socks5 (line 289) | type proxy_socks5 struct
    method Dial (line 323) | func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
    method connect (line 344) | func (s *proxy_socks5) connect(conn net.Conn, target string) error {
  constant proxy_socks5Version (line 295) | proxy_socks5Version = 5
  constant proxy_socks5AuthNone (line 298) | proxy_socks5AuthNone     = 0
  constant proxy_socks5AuthPassword (line 299) | proxy_socks5AuthPassword = 2
  constant proxy_socks5Connect (line 302) | proxy_socks5Connect = 1
  constant proxy_socks5IP4 (line 305) | proxy_socks5IP4    = 1
  constant proxy_socks5Domain (line 306) | proxy_socks5Domain = 3
  constant proxy_socks5IP6 (line 307) | proxy_socks5IP6    = 4

FILE: websockets/v2/CLI.go
  type CLI (line 13) | type CLI struct
    method PlayPoker (line 39) | func (cli *CLI) PlayPoker() {
    method readLine (line 69) | func (cli *CLI) readLine() string {
  function NewCLI (line 21) | func NewCLI(in io.Reader, out io.Writer, game Game) *CLI {
  constant PlayerPrompt (line 30) | PlayerPrompt = "Please enter the number of players: "
  constant BadPlayerInputErrMsg (line 33) | BadPlayerInputErrMsg = "Bad value received for number of players, please...
  constant BadWinnerInputMsg (line 36) | BadWinnerInputMsg = "invalid winner input, expect format of 'PlayerName ...
  function extractWinner (line 62) | func extractWinner(userInput string) (string, error) {

FILE: websockets/v2/CLI_test.go
  type GameSpy (line 18) | type GameSpy struct
    method Start (line 27) | func (g *GameSpy) Start(numberOfPlayers int, out io.Writer) {
    method Finish (line 33) | func (g *GameSpy) Finish(winner string) {
  function userSends (line 38) | func userSends(messages ...string) io.Reader {
  function TestCLI (line 42) | func TestCLI(t *testing.T) {
  function assertGameStartedWith (line 93) | func assertGameStartedWith(t testing.TB, game *GameSpy, numberOfPlayersW...
  function assertGameNotFinished (line 105) | func assertGameNotFinished(t testing.TB, game *GameSpy) {
  function assertGameNotStarted (line 112) | func assertGameNotStarted(t testing.TB, game *GameSpy) {
  function assertFinishCalledWith (line 119) | func assertFinishCalledWith(t testing.TB, game *GameSpy, winner string) {
  function assertMessagesSentToUser (line 131) | func assertMessagesSentToUser(t testing.TB, stdout *bytes.Buffer, messag...
  function assertScheduledAlert (line 140) | func assertScheduledAlert(t testing.TB, got, want poker.ScheduledAlert) {

FILE: websockets/v2/blind_alerter.go
  type BlindAlerter (line 10) | type BlindAlerter interface
  type BlindAlerterFunc (line 15) | type BlindAlerterFunc
    method ScheduleAlertAt (line 18) | func (a BlindAlerterFunc) ScheduleAlertAt(duration time.Duration, amou...
  function Alerter (line 23) | func Alerter(duration time.Duration, amount int, to io.Writer) {

FILE: websockets/v2/cmd/cli/main.go
  constant dbFileName (line 11) | dbFileName = "game.db.json"
  function main (line 13) | func main() {

FILE: websockets/v2/cmd/webserver/main.go
  constant dbFileName (line 10) | dbFileName = "game.db.json"
  function main (line 12) | func main() {

FILE: websockets/v2/file_system_store.go
  type FileSystemPlayerStore (line 12) | type FileSystemPlayerStore struct
    method GetLeague (line 77) | func (f *FileSystemPlayerStore) GetLeague() League {
    method GetPlayerScore (line 85) | func (f *FileSystemPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 97) | func (f *FileSystemPlayerStore) RecordWin(name string) {
  function NewFileSystemPlayerStore (line 18) | func NewFileSystemPlayerStore(file *os.File) (*FileSystemPlayerStore, er...
  function FileSystemPlayerStoreFromFile (line 39) | func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore,...
  function initialisePlayerDBFile (line 59) | func initialisePlayerDBFile(file *os.File) error {

FILE: websockets/v2/file_system_store_test.go
  function createTempFile (line 10) | func createTempFile(t testing.TB, initialData string) (*os.File, func()) {
  function TestFileSystemStore (line 29) | func TestFileSystemStore(t *testing.T) {
  function assertScoreEquals (line 114) | func assertScoreEquals(t testing.TB, got, want int) {
  function assertNoError (line 121) | func assertNoError(t testing.TB, err error) {

FILE: websockets/v2/game.go
  type Game (line 6) | type Game interface

FILE: websockets/v2/league.go
  type League (line 10) | type League
    method Find (line 13) | func (l League) Find(name string) *Player {
  function NewLeague (line 23) | func NewLeague(rdr io.Reader) (League, error) {

FILE: websockets/v2/player_server_ws.go
  type playerServerWS (line 9) | type playerServerWS struct
    method Write (line 13) | func (w *playerServerWS) Write(p []byte) (n int, err error) {
    method WaitForMsg (line 33) | func (w *playerServerWS) WaitForMsg() string {
  function newPlayerServerWS (line 23) | func newPlayerServerWS(w http.ResponseWriter, r *http.Request) *playerSe...

FILE: websockets/v2/server.go
  type PlayerStore (line 15) | type PlayerStore interface
  type Player (line 22) | type Player struct
  type PlayerServer (line 28) | type PlayerServer struct
    method webSocket (line 68) | func (p *PlayerServer) webSocket(w http.ResponseWriter, r *http.Reques...
    method playGame (line 79) | func (p *PlayerServer) playGame(w http.ResponseWriter, r *http.Request) {
    method leagueHandler (line 83) | func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Re...
    method playersHandler (line 88) | func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.R...
    method showScore (line 99) | func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
    method processWin (line 109) | func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
  constant jsonContentType (line 35) | jsonContentType = "application/json"
  constant htmlTemplatePath (line 36) | htmlTemplatePath = "game.html"
  function NewPlayerServer (line 39) | func NewPlayerServer(store PlayerStore, game Game) (*PlayerServer, error) {

FILE: websockets/v2/server_integration_test.go
  function TestRecordingWinsAndRetrievingThem (line 10) | func TestRecordingWinsAndRetrievingThem(t *testing.T) {

FILE: websockets/v2/server_test.go
  function mustMakePlayerServer (line 22) | func mustMakePlayerServer(t *testing.T, store poker.PlayerStore, game po...
  function TestGETPlayers (line 30) | func TestGETPlayers(t *testing.T) {
  function TestStoreWins (line 69) | func TestStoreWins(t *testing.T) {
  function TestLeague (line 88) | func TestLeague(t *testing.T) {
  function TestGame (line 114) | func TestGame(t *testing.T) {
  function assertWebsocketGotMsg (line 146) | func assertWebsocketGotMsg(t *testing.T, ws *websocket.Conn, want string) {
  function retryUntil (line 153) | func retryUntil(d time.Duration, f func() bool) bool {
  function within (line 163) | func within(t testing.TB, d time.Duration, assert func()) {
  function writeWSMessage (line 180) | func writeWSMessage(t testing.TB, conn *websocket.Conn, message string) {
  function assertContentType (line 187) | func assertContentType(t testing.TB, response *httptest.ResponseRecorder...
  function getLeagueFromResponse (line 194) | func getLeagueFromResponse(t *testing.T, body io.Reader) []poker.Player {
  function assertLeague (line 205) | func assertLeague(t *testing.T, got, want []poker.Player) {
  function assertStatus (line 212) | func assertStatus(t *testing.T, got *httptest.ResponseRecorder, want int) {
  function newGameRequest (line 219) | func newGameRequest() *http.Request {
  function newLeagueRequest (line 224) | func newLeagueRequest() *http.Request {
  function newGetScoreRequest (line 229) | func newGetScoreRequest(name string) *http.Request {
  function newPostWinRequest (line 234) | func newPostWinRequest(name string) *http.Request {
  function assertResponseBody (line 239) | func assertResponseBody(t *testing.T, got, want string) {
  function mustDialWS (line 246) | func mustDialWS(t *testing.T, url string) *websocket.Conn {

FILE: websockets/v2/tape.go
  type Tape (line 9) | type Tape struct
    method Write (line 13) | func (t *Tape) Write(p []byte) (n int, err error) {

FILE: websockets/v2/tape_test.go
  function TestTape_Write (line 10) | func TestTape_Write(t *testing.T) {

FILE: websockets/v2/testing.go
  type StubPlayerStore (line 11) | type StubPlayerStore struct
    method GetPlayerScore (line 18) | func (s *StubPlayerStore) GetPlayerScore(name string) int {
    method RecordWin (line 24) | func (s *StubPlayerStore) RecordWin(name string) {
    method GetLeague (line 29) | func (s *StubPlayerStore) GetLeague() League {
  function AssertPlayerWin (line 34) | func AssertPlayerWin(t testing.TB, store *StubPlayerStore, winner string) {
  type ScheduledAlert (line 47) | type ScheduledAlert struct
    method String (line 52) | func (s ScheduledAlert) String() string {
  type SpyBlindAlerter (line 57) | type SpyBlindAlerter struct
    method ScheduleAlertAt (line 62) | func (s *SpyBlindAlerter) ScheduleAlertAt(at time.Duration, amount int...

FILE: websockets/v2/texas_holdem.go
  type TexasHoldem (line 9) | type TexasHoldem struct
    method Start (line 23) | func (p *TexasHoldem) Start(numberOfPlayers int, alertsDestination io....
    method Finish (line 35) | func (p *TexasHoldem) Finish(winner string) {
  function NewTexasHoldem (line 15) | func NewTexasHoldem(alerter BlindAlerter, store PlayerStore) *TexasHoldem {

FILE: websockets/v2/texas_holdem_test.go
  function TestGame_Start (line 12) | func TestGame_Start(t *testing.T) {
  function TestGame_Finish (line 54) | func TestGame_Finish(t *testing.T) {
  function checkSchedulingCases (line 63) | func checkSchedulingCases(cases []poker.ScheduledAlert, t *testing.T, bl...

FILE: websockets/v2/vendor/github.com/gorilla/websocket/client.go
  function NewClient (line 39) | func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, ...
  type Dialer (line 51) | type Dialer struct
    method Dial (line 105) | func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn...
    method DialContext (line 149) | func (d *Dialer) DialContext(ctx context.Context, urlStr string, reque...
  function hostPortNoPort (line 111) | func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
  function doHandshake (line 385) | func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {

FILE: websockets/v2/vendor/github.com/gorilla/websocket/client_clone.go
  function cloneTLSConfig (line 11) | func cloneTLSConfig(cfg *tls.Config) *tls.Config {

FILE: websockets/v2/vendor/github.com/gorilla/websocket/client_clone_legacy.go
  function cloneTLSConfig (line 15) | func cloneTLSConfig(cfg *tls.Config) *tls.Config {

FILE: websockets/v2/vendor/github.com/gorilla/websocket/compression.go
  constant minCompressionLevel (line 16) | minCompressionLevel     = -2
  constant maxCompressionLevel (line 17) | maxCompressionLevel     = flate.BestCompression
  constant defaultCompressionLevel (line 18) | defaultCompressionLevel = 1
  function decompressNoContextTakeover (line 28) | func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
  function isValidCompressionLevel (line 40) | func isValidCompressionLevel(level int) bool {
  function compressNoContextTakeover (line 44) | func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteClos...
  type truncWriter (line 58) | type truncWriter struct
    method Write (line 64) | func (w *truncWriter) Write(p []byte) (int, error) {
  type flateWriteWrapper (line 92) | type flateWriteWrapper struct
    method Write (line 98) | func (w *flateWriteWrapper) Write(p []byte) (int, error) {
    method Close (line 105) | func (w *flateWriteWrapper) Close() error {
  type flateReadWrapper (line 122) | type flateReadWrapper struct
    method Read (line 126) | func (r *flateReadWrapper) Read(p []byte) (int, error) {
    method Close (line 140) | func (r *flateReadWrapper) Close() error {

FILE: websockets/v2/vendor/github.com/gorilla/websocket/conn.go
  constant finalBit (line 23) | finalBit = 1 << 7
  constant rsv1Bit (line 24) | rsv1Bit  = 1 << 6
  constant rsv2Bit (line 25) | rsv2Bit  = 1 << 5
  constant rsv3Bit (line 26) | rsv3Bit  = 1 << 4
  constant maskBit (line 29) | maskBit = 1 << 7
  constant maxFrameHeaderSize (line 31) | maxFrameHeaderSize         = 2 + 8 + 4
  constant maxControlFramePayloadSize (line 32) | maxControlFramePayloadSize = 125
  constant writeWait (line 34) | writeWait = time.Second
  constant defaultReadBufferSize (line 36) | defaultReadBufferSize  = 4096
  constant defaultWriteBufferSize (line 37) | defaultWriteBufferSize = 4096
  constant continuationFrame (line 39) | continuationFrame = 0
  constant noFrame (line 40) | noFrame           = -1
  constant CloseNormalClosure (line 45) | CloseNormalClosure           = 1000
  constant CloseGoingAway (line 46) | CloseGoingAway               = 1001
  constant CloseProtocolError (line 47) | CloseProtocolError           = 1002
  constant CloseUnsupportedData (line 48) | CloseUnsupportedData         = 1003
  constant CloseNoStatusReceived (line 49) | CloseNoStatusReceived        = 1005
  constant CloseAbnormalClosure (line 50) | CloseAbnormalClosure         = 1006
  constant CloseInvalidFramePayloadData (line 51) | CloseInvalidFramePayloadData = 1007
  constant ClosePolicyViolation (line 52) | ClosePolicyViolation         = 1008
  constant CloseMessageTooBig (line 53) | CloseMessageTooBig           = 1009
  constant CloseMandatoryExtension (line 54) | CloseMandatoryExtension      = 1010
  constant CloseInternalServerErr (line 55) | CloseInternalServerErr       = 1011
  constant CloseServiceRestart (line 56) | CloseServiceRestart          = 1012
  constant CloseTryAgainLater (line 57) | CloseTryAgainLater           = 1013
  constant CloseTLSHandshake (line 58) | CloseTLSHandshake            = 1015
  constant TextMessage (line 65) | TextMessage = 1
  constant BinaryMessage (line 68) | BinaryMessage = 2
  constant CloseMessage (line 73) | CloseMessage = 8
  constant PingMessage (line 77) | PingMessage = 9
  constant PongMessage (line 81) | PongMessage = 10
  type netError (line 93) | type netError struct
    method Error (line 99) | func (e *netError) Error() string
Condensed preview — 599 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,714K chars).
[
  {
    "path": ".editorconfig",
    "chars": 588,
    "preview": "# Top-most EditorConfig file\nroot = true\n\n# Every file should according to these default configurations if not specified"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 62,
    "preview": "# These are supported funding model platforms\n\ngithub: [quii]\n"
  },
  {
    "path": ".github/workflows/go.yml",
    "chars": 1513,
    "preview": "name: Go\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  release:\n    types: [ published ]\n"
  },
  {
    "path": ".gitignore",
    "chars": 135,
    "preview": ".idea/\n*.iml\n\n# Book build output\n_book/\n\n# eBook build output\n*.epub\n*.mobi\n*.pdf\n\n# templated files\nmeta.tex\n\ngame.db."
  },
  {
    "path": ".mdlrc",
    "chars": 898,
    "preview": "git_recurse true\n\nrules \"MD001\", \"MD002\", \"MD003\", \"MD004\", \"MD005\", \"MD006\", \"MD007\", \"MD008\", \"MD009\", \"MD010\", \"MD011"
  },
  {
    "path": "LICENSE.md",
    "chars": 1078,
    "preview": "MIT License\n\nCopyright (c) [2025] [Christopher James]\n\nPermission is hereby granted, free of charge, to any person obtai"
  },
  {
    "path": "README.md",
    "chars": 10773,
    "preview": "# Learn Go with Tests\n\n<p align=\"center\">\n  <img src=\"red-green-blue-gophers-smaller.png\" />\n</p>\n\n[Art by Denise](https"
  },
  {
    "path": "SUMMARY.md",
    "chars": 1707,
    "preview": "# Table of contents\n\n* [Learn Go with Tests](gb-readme.md)\n\n## Go fundamentals\n\n* [Install Go](install-go.md)\n* [Hello, "
  },
  {
    "path": "anti-patterns.md",
    "chars": 13201,
    "preview": "# TDD Anti-patterns\n\nFrom time to time it's necessary to review your TDD techniques and remind yourself of behaviours to"
  },
  {
    "path": "app-intro.md",
    "chars": 1279,
    "preview": "# Build an application\n\nNow that you have hopefully digested the _Go Fundamentals_ section you have a solid grounding of"
  },
  {
    "path": "arrays/v1/sum.go",
    "chars": 172,
    "preview": "package main\n\n// Sum calculates the total from an array of numbers.\nfunc Sum(numbers [5]int) int {\n\tsum := 0\n\tfor i := 0"
  },
  {
    "path": "arrays/v1/sum_test.go",
    "chars": 213,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestSum(t *testing.T) {\n\n\tnumbers := [5]int{1, 2, 3, 4, 5}\n\n\tgot := Sum(numbers)\n\tw"
  },
  {
    "path": "arrays/v2/sum.go",
    "chars": 176,
    "preview": "package main\n\n// Sum calculates the total from an array of numbers.\nfunc Sum(numbers [5]int) int {\n\tsum := 0\n\tfor _, num"
  },
  {
    "path": "arrays/v2/sum_test.go",
    "chars": 213,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestSum(t *testing.T) {\n\n\tnumbers := [5]int{1, 2, 3, 4, 5}\n\n\tgot := Sum(numbers)\n\tw"
  },
  {
    "path": "arrays/v3/sum.go",
    "chars": 174,
    "preview": "package main\n\n// Sum calculates the total from a slice of numbers.\nfunc Sum(numbers []int) int {\n\tsum := 0\n\tfor _, numbe"
  },
  {
    "path": "arrays/v3/sum_test.go",
    "chars": 272,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestSum(t *testing.T) {\n\n\tt.Run(\"collections of any size\", func(t *testing.T) {\n\n\t\t"
  },
  {
    "path": "arrays/v4/sum.go",
    "chars": 446,
    "preview": "package main\n\n// Sum calculates the total from a slice of numbers.\nfunc Sum(numbers []int) int {\n\tsum := 0\n\tfor _, numbe"
  },
  {
    "path": "arrays/v4/sum_test.go",
    "chars": 459,
    "preview": "package main\n\nimport (\n\t\"slices\"\n\t\"testing\"\n)\n\nfunc TestSum(t *testing.T) {\n\n\tt.Run(\"collections of any size\", func(t *t"
  },
  {
    "path": "arrays/v5/sum.go",
    "chars": 396,
    "preview": "package main\n\n// Sum calculates the total from a slice of numbers.\nfunc Sum(numbers []int) int {\n\tsum := 0\n\tfor _, numbe"
  },
  {
    "path": "arrays/v5/sum_test.go",
    "chars": 460,
    "preview": "package main\n\nimport (\n\t\"slices\"\n\t\"testing\"\n)\n\nfunc TestSum(t *testing.T) {\n\n\tt.Run(\"collections of any size\", func(t *t"
  },
  {
    "path": "arrays/v6/sum.go",
    "chars": 425,
    "preview": "package main\n\n// Sum calculates the total from a slice of numbers.\nfunc Sum(numbers []int) int {\n\tsum := 0\n\tfor _, numbe"
  },
  {
    "path": "arrays/v6/sum_test.go",
    "chars": 470,
    "preview": "package main\n\nimport (\n\t\"slices\"\n\t\"testing\"\n)\n\nfunc TestSum(t *testing.T) {\n\n\tt.Run(\"collections of any size\", func(t *t"
  },
  {
    "path": "arrays/v7/sum.go",
    "chars": 514,
    "preview": "package main\n\n// Sum calculates the total from a slice of numbers.\nfunc Sum(numbers []int) int {\n\tsum := 0\n\tfor _, numbe"
  },
  {
    "path": "arrays/v7/sum_test.go",
    "chars": 772,
    "preview": "package main\n\nimport (\n\t\"slices\"\n\t\"testing\"\n)\n\nfunc TestSum(t *testing.T) {\n\n\tt.Run(\"collections of any size\", func(t *t"
  },
  {
    "path": "arrays/v8/assert.go",
    "chars": 518,
    "preview": "package main\n\nimport \"testing\"\n\nfunc AssertEqual[T comparable](t *testing.T, got, want T) {\n\tt.Helper()\n\tif got != want "
  },
  {
    "path": "arrays/v8/bad_bank.go",
    "chars": 624,
    "preview": "package main\n\ntype Transaction struct {\n\tFrom string\n\tTo   string\n\tSum  float64\n}\n\nfunc NewTransaction(from, to Account,"
  },
  {
    "path": "arrays/v8/bad_bank_test.go",
    "chars": 559,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestBadBank(t *testing.T) {\n\tvar (\n\t\triya  = Account{Name: \"Riya\", Balance: 100}\n\t\t"
  },
  {
    "path": "arrays/v8/collection_fun.go",
    "chars": 350,
    "preview": "package main\n\nfunc Find[A any](items []A, predicate func(A) bool) (value A, found bool) {\n\tfor _, v := range items {\n\t\ti"
  },
  {
    "path": "arrays/v8/sum.go",
    "chars": 512,
    "preview": "package main\n\n// Sum calculates the total from a slice of numbers.\nfunc Sum(numbers []int) int {\n\tadd := func(acc, x int"
  },
  {
    "path": "arrays/v8/sum_test.go",
    "chars": 1880,
    "preview": "package main\n\nimport (\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestSum(t *testing.T) {\n\n\tt.Run(\"collections of any size\""
  },
  {
    "path": "arrays-and-slices.md",
    "chars": 16708,
    "preview": "# Arrays and slices\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tre"
  },
  {
    "path": "blogrenderer/post.go",
    "chars": 371,
    "preview": "package blogrenderer\n\nimport \"strings\"\n\n// Post is a representation of a post\ntype Post struct {\n\tTitle, Description, Bo"
  },
  {
    "path": "blogrenderer/renderer.go",
    "chars": 1301,
    "preview": "package blogrenderer\n\nimport (\n\t\"embed\"\n\t\"github.com/gomarkdown/markdown\"\n\t\"github.com/gomarkdown/markdown/parser\"\n\t\"htm"
  },
  {
    "path": "blogrenderer/renderer_test.TestRender.it_converts_a_single_post_into_HTML.approved.txt",
    "chars": 1032,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <title>My amazing blog!</title>\n    <meta charset=\"UTF-8\"/>\n    <meta name=\""
  },
  {
    "path": "blogrenderer/renderer_test.TestRender.it_renders_an_index_of_posts.approved.txt",
    "chars": 802,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <title>My amazing blog!</title>\n    <meta charset=\"UTF-8\"/>\n    <meta name=\""
  },
  {
    "path": "blogrenderer/renderer_test.go",
    "chars": 1559,
    "preview": "package blogrenderer_test\n\nimport (\n\t\"bytes\"\n\tapprovals \"github.com/approvals/go-approval-tests\"\n\t\"github.com/quii/learn"
  },
  {
    "path": "blogrenderer/templates/blog.gohtml",
    "chars": 157,
    "preview": "{{template \"top\" .}}\n<h1>{{.Title}}</h1>\n\n<p>{{.Description}}</p>\n\nTags: <ul>{{range .Tags}}<li>{{.}}</li>{{end}}</ul>\n{"
  },
  {
    "path": "blogrenderer/templates/bottom.gohtml",
    "chars": 216,
    "preview": "{{define \"bottom\"}}\n</main>\n<footer>\n    <ul>\n        <li><a href=\"https://twitter.com/quii\">Twitter</a></li>\n        <l"
  },
  {
    "path": "blogrenderer/templates/index.gohtml",
    "chars": 132,
    "preview": "{{template \"top\" .}}\n<ol>{{range .}}<li><a href=\"/post/{{.SanitisedTitle}}\">{{.Title}}</a></li>{{end}}</ol>\n{{template \""
  },
  {
    "path": "blogrenderer/templates/top.gohtml",
    "chars": 517,
    "preview": "{{define \"top\"}}<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <title>My amazing blog!</title>\n    <meta charset=\"UTF-8\"/>\n"
  },
  {
    "path": "book.json",
    "chars": 62,
    "preview": "{\n    \"structure\": {\n        \"readme\": \"gb-readme.md\"\n    }\n}\n"
  },
  {
    "path": "build.books.sh",
    "chars": 2448,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\n# safer separator for sed\nsep=$'\\001'\n\nif [ -v GITHUB_REF_NAME ]; then\n    sed \"s${sep}%%FO"
  },
  {
    "path": "build.sh",
    "chars": 315,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ngo install github.com/client9/misspell/cmd/misspell@latest\ngo install github.com/po3rin/gof"
  },
  {
    "path": "command-line/v1/cmd/cli/main.go",
    "chars": 77,
    "preview": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"Let's play poker\")\n}\n"
  },
  {
    "path": "command-line/v1/cmd/webserver/main.go",
    "chars": 529,
    "preview": "package main\n\nimport (\n\t\"github.com/quii/learn-go-with-tests/command-line/v1\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n)\n\nconst dbFileNa"
  },
  {
    "path": "command-line/v1/file_system_store.go",
    "chars": 1795,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n)\n\n// FileSystemPlayerStore stores players in the fi"
  },
  {
    "path": "command-line/v1/file_system_store_test.go",
    "chars": 2516,
    "preview": "package poker\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc createTempFile(t testing.TB, initialData string) (*os.File, func()) {\n\t"
  },
  {
    "path": "command-line/v1/league.go",
    "chars": 549,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// League stores a collection of players.\ntype League []Player\n"
  },
  {
    "path": "command-line/v1/server.go",
    "chars": 1628,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about "
  },
  {
    "path": "command-line/v1/server_integration_test.go",
    "chars": 1085,
    "preview": "package poker\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestRecordingWinsAndRetrievingThem(t *testin"
  },
  {
    "path": "command-line/v1/server_test.go",
    "chars": 4002,
    "preview": "package poker\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype StubPlayerStore stru"
  },
  {
    "path": "command-line/v1/tape.go",
    "chars": 204,
    "preview": "package poker\n\nimport (\n\t\"io\"\n\t\"os\"\n)\n\ntype tape struct {\n\tfile *os.File\n}\n\nfunc (t *tape) Write(p []byte) (n int, err e"
  },
  {
    "path": "command-line/v1/tape_test.go",
    "chars": 369,
    "preview": "package poker\n\nimport (\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestTape_Write(t *testing.T) {\n\tfile, clean := createTempFile(t, \"12345"
  },
  {
    "path": "command-line/v2/CLI.go",
    "chars": 204,
    "preview": "package poker\n\n// CLI helps players through a game of poker.\ntype CLI struct {\n\tplayerStore PlayerStore\n}\n\n// PlayPoker "
  },
  {
    "path": "command-line/v2/CLI_test.go",
    "chars": 241,
    "preview": "package poker\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCLI(t *testing.T) {\n\tplayerStore := &StubPlayerStore{}\n\n\tcli := &CLI{play"
  },
  {
    "path": "command-line/v2/cmd/cli/main.go",
    "chars": 77,
    "preview": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"Let's play poker\")\n}\n"
  },
  {
    "path": "command-line/v2/cmd/webserver/main.go",
    "chars": 529,
    "preview": "package main\n\nimport (\n\t\"github.com/quii/learn-go-with-tests/command-line/v1\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n)\n\nconst dbFileNa"
  },
  {
    "path": "command-line/v2/file_system_store.go",
    "chars": 1795,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n)\n\n// FileSystemPlayerStore stores players in the fi"
  },
  {
    "path": "command-line/v2/file_system_store_test.go",
    "chars": 2516,
    "preview": "package poker\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc createTempFile(t testing.TB, initialData string) (*os.File, func()) {\n\t"
  },
  {
    "path": "command-line/v2/league.go",
    "chars": 549,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// League stores a collection of players.\ntype League []Player\n"
  },
  {
    "path": "command-line/v2/server.go",
    "chars": 1628,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about "
  },
  {
    "path": "command-line/v2/server_integration_test.go",
    "chars": 1085,
    "preview": "package poker\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestRecordingWinsAndRetrievingThem(t *testin"
  },
  {
    "path": "command-line/v2/server_test.go",
    "chars": 4002,
    "preview": "package poker\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype StubPlayerStore stru"
  },
  {
    "path": "command-line/v2/tape.go",
    "chars": 204,
    "preview": "package poker\n\nimport (\n\t\"io\"\n\t\"os\"\n)\n\ntype tape struct {\n\tfile *os.File\n}\n\nfunc (t *tape) Write(p []byte) (n int, err e"
  },
  {
    "path": "command-line/v2/tape_test.go",
    "chars": 369,
    "preview": "package poker\n\nimport (\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestTape_Write(t *testing.T) {\n\tfile, clean := createTempFile(t, \"12345"
  },
  {
    "path": "command-line/v3/CLI.go",
    "chars": 667,
    "preview": "package poker\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"strings\"\n)\n\n// CLI helps players through a game of poker.\ntype CLI struct {\n\tpl"
  },
  {
    "path": "command-line/v3/CLI_test.go",
    "chars": 1183,
    "preview": "package poker_test\n\nimport (\n\t\"github.com/quii/learn-go-with-tests/command-line/v3\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc "
  },
  {
    "path": "command-line/v3/cmd/cli/main.go",
    "chars": 408,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tpoker \"github.com/quii/learn-go-with-tests/command-line/v3\"\n)\n\nconst dbFile"
  },
  {
    "path": "command-line/v3/cmd/webserver/main.go",
    "chars": 372,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\tpoker \"github.com/quii/learn-go-with-tests/command-line/v3\"\n)\n\nconst dbFileN"
  },
  {
    "path": "command-line/v3/file_system_store.go",
    "chars": 2373,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n)\n\n// FileSystemPlayerStore stores players in the fi"
  },
  {
    "path": "command-line/v3/file_system_store_test.go",
    "chars": 2516,
    "preview": "package poker\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc createTempFile(t testing.TB, initialData string) (*os.File, func()) {\n\t"
  },
  {
    "path": "command-line/v3/league.go",
    "chars": 549,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// League stores a collection of players.\ntype League []Player\n"
  },
  {
    "path": "command-line/v3/server.go",
    "chars": 1628,
    "preview": "package poker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about "
  },
  {
    "path": "command-line/v3/server_integration_test.go",
    "chars": 1085,
    "preview": "package poker\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestRecordingWinsAndRetrievingThem(t *testin"
  },
  {
    "path": "command-line/v3/server_test.go",
    "chars": 3441,
    "preview": "package poker\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestGETPlayers(t *te"
  },
  {
    "path": "command-line/v3/tape.go",
    "chars": 204,
    "preview": "package poker\n\nimport (\n\t\"io\"\n\t\"os\"\n)\n\ntype tape struct {\n\tfile *os.File\n}\n\nfunc (t *tape) Write(p []byte) (n int, err e"
  },
  {
    "path": "command-line/v3/tape_test.go",
    "chars": 369,
    "preview": "package poker\n\nimport (\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestTape_Write(t *testing.T) {\n\tfile, clean := createTempFile(t, \"12345"
  },
  {
    "path": "command-line/v3/testing.go",
    "chars": 971,
    "preview": "package poker\n\nimport \"testing\"\n\n// StubPlayerStore implements PlayerStore for testing purposes.\ntype StubPlayerStore st"
  },
  {
    "path": "command-line.md",
    "chars": 21700,
    "preview": "# Command line and project structure\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-"
  },
  {
    "path": "concurrency/v1/check_website.go",
    "chars": 277,
    "preview": "package concurrency\n\nimport \"net/http\"\n\n// CheckWebsite returns true if the URL returns a 200 status code, false otherwi"
  },
  {
    "path": "concurrency/v1/check_websites.go",
    "chars": 451,
    "preview": "package concurrency\n\n// WebsiteChecker checks a url, returning a bool.\ntype WebsiteChecker func(string) bool\n\n// CheckWe"
  },
  {
    "path": "concurrency/v1/check_websites_benchmark_test.go",
    "chars": 346,
    "preview": "package concurrency\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc slowStubWebsiteChecker(_ string) bool {\n\ttime.Sleep(20 * time.M"
  },
  {
    "path": "concurrency/v1/check_websites_test.go",
    "chars": 603,
    "preview": "package concurrency\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc mockWebsiteChecker(url string) bool {\n\tif url == \"waat://fur"
  },
  {
    "path": "concurrency/v2/check_website.go",
    "chars": 277,
    "preview": "package concurrency\n\nimport \"net/http\"\n\n// CheckWebsite returns true if the URL returns a 200 status code, false otherwi"
  },
  {
    "path": "concurrency/v2/check_websites.go",
    "chars": 522,
    "preview": "package concurrency\n\nimport (\n\t\"time\"\n)\n\n// WebsiteChecker checks a url, returning a bool.\ntype WebsiteChecker func(stri"
  },
  {
    "path": "concurrency/v2/check_websites_benchmark_test.go",
    "chars": 346,
    "preview": "package concurrency\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc slowStubWebsiteChecker(_ string) bool {\n\ttime.Sleep(20 * time.M"
  },
  {
    "path": "concurrency/v2/check_websites_test.go",
    "chars": 603,
    "preview": "package concurrency\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc mockWebsiteChecker(url string) bool {\n\tif url == \"waat://fur"
  },
  {
    "path": "concurrency/v3/check_website.go",
    "chars": 277,
    "preview": "package concurrency\n\nimport \"net/http\"\n\n// CheckWebsite returns true if the URL returns a 200 status code, false otherwi"
  },
  {
    "path": "concurrency/v3/check_websites.go",
    "chars": 649,
    "preview": "package concurrency\n\n// WebsiteChecker checks a url, returning a bool.\ntype WebsiteChecker func(string) bool\ntype result"
  },
  {
    "path": "concurrency/v3/check_websites_benchmark_test.go",
    "chars": 346,
    "preview": "package concurrency\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc slowStubWebsiteChecker(_ string) bool {\n\ttime.Sleep(20 * time.M"
  },
  {
    "path": "concurrency/v3/check_websites_test.go",
    "chars": 603,
    "preview": "package concurrency\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc mockWebsiteChecker(url string) bool {\n\tif url == \"waat://fur"
  },
  {
    "path": "concurrency.md",
    "chars": 17300,
    "preview": "# Concurrency\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main"
  },
  {
    "path": "context/v1/context.go",
    "chars": 296,
    "preview": "package context1\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Store fetches data.\ntype Store interface {\n\tFetch() string\n}\n\n// Ser"
  },
  {
    "path": "context/v1/context_test.go",
    "chars": 503,
    "preview": "package context1\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\ntype StubStore struct {\n\tresponse string\n}\n\nfu"
  },
  {
    "path": "context/v2/context.go",
    "chars": 469,
    "preview": "package context2\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Store fetches data.\ntype Store interface {\n\tFetch() string\n\tCancel()"
  },
  {
    "path": "context/v2/context_test.go",
    "chars": 1026,
    "preview": "package context2\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestServer(t *testing."
  },
  {
    "path": "context/v2/testdoubles.go",
    "chars": 669,
    "preview": "package context2\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\n// SpyStore allows you to simulate a store and see how its used.\ntype S"
  },
  {
    "path": "context/v3/context.go",
    "chars": 436,
    "preview": "package context3\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Store fetches data.\ntype Store interface {\n\tFetch(ctx con"
  },
  {
    "path": "context/v3/context_test.go",
    "chars": 1032,
    "preview": "package context3\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestServer(t *testing."
  },
  {
    "path": "context/v3/testdoubles.go",
    "chars": 1196,
    "preview": "package context3\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n)\n\n// SpyStore allows you to simulate a store"
  },
  {
    "path": "context-aware-reader.md",
    "chars": 9665,
    "preview": "# Context-aware readers\n\n**[You can find all the code here](https://github.com/quii/learn-go-with-tests/tree/main/q-and-"
  },
  {
    "path": "context.md",
    "chars": 16851,
    "preview": "# Context\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main/con"
  },
  {
    "path": "contributing.md",
    "chars": 2337,
    "preview": "# Contributing\n\nContributions are very welcome. I hope for this to become a great home for guides of how to learn Go by "
  },
  {
    "path": "dependency-injection.md",
    "chars": 8275,
    "preview": "# Dependency Injection\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/"
  },
  {
    "path": "di/v1/di.go",
    "chars": 226,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n)\n\n// Greet sends a personalised greeting to writer.\nfunc Greet(writer io.Writ"
  },
  {
    "path": "di/v1/di_test.go",
    "chars": 241,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestGreet(t *testing.T) {\n\tbuffer := bytes.Buffer{}\n\tGreet(&buffer, \""
  },
  {
    "path": "di/v2/di.go",
    "chars": 423,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n)\n\n// Greet sends a personalised greeting to writer.\nfunc Greet(w"
  },
  {
    "path": "di/v2/di_test.go",
    "chars": 241,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestGreet(t *testing.T) {\n\tbuffer := bytes.Buffer{}\n\tGreet(&buffer, \""
  },
  {
    "path": "error-types.md",
    "chars": 6761,
    "preview": "# Error types\n\n**[You can find all the code here](https://github.com/quii/learn-go-with-tests/tree/main/q-and-a/error-ty"
  },
  {
    "path": "for/v1/repeat.go",
    "chars": 207,
    "preview": "package iteration\n\n// Repeat returns character repeated 5 times.\nfunc Repeat(character string) string {\n\tvar repeated st"
  },
  {
    "path": "for/v1/repeat_test.go",
    "chars": 205,
    "preview": "package iteration\n\nimport \"testing\"\n\nfunc TestRepeat(t *testing.T) {\n\trepeated := Repeat(\"a\")\n\texpected := \"aaaaa\"\n\n\tif "
  },
  {
    "path": "for/v2/repeat.go",
    "chars": 230,
    "preview": "package iteration\n\nconst repeatCount = 5\n\n// Repeat returns character repeated 5 times.\nfunc Repeat(character string) st"
  },
  {
    "path": "for/v2/repeat_test.go",
    "chars": 205,
    "preview": "package iteration\n\nimport \"testing\"\n\nfunc TestRepeat(t *testing.T) {\n\trepeated := Repeat(\"a\")\n\texpected := \"aaaaa\"\n\n\tif "
  },
  {
    "path": "for/v3/repeat.go",
    "chars": 276,
    "preview": "package iteration\n\nimport \"strings\"\n\nconst repeatCount = 5\n\n// Repeat returns character repeated 5 times.\nfunc Repeat(ch"
  },
  {
    "path": "for/v3/repeat_test.go",
    "chars": 205,
    "preview": "package iteration\n\nimport \"testing\"\n\nfunc TestRepeat(t *testing.T) {\n\trepeated := Repeat(\"a\")\n\texpected := \"aaaaa\"\n\n\tif "
  },
  {
    "path": "for/vx/repeat.go",
    "chars": 230,
    "preview": "package iteration\n\nconst repeatCount = 5\n\n// Repeat returns character repeated 5 times.\nfunc Repeat(character string) st"
  },
  {
    "path": "for/vx/repeat_test.go",
    "chars": 278,
    "preview": "package iteration\n\nimport \"testing\"\n\nfunc TestRepeat(t *testing.T) {\n\trepeated := Repeat(\"a\")\n\texpected := \"aaaaa\"\n\n\tif "
  },
  {
    "path": "gb-readme.md",
    "chars": 4265,
    "preview": "# Learn Go with Tests\n\n![](.gitbook/assets/red-green-blue-gophers-smaller.png)\n\n[Art by Denise](https://twitter.com/deni"
  },
  {
    "path": "generics/assert.go",
    "chars": 522,
    "preview": "package generics\n\nimport \"testing\"\n\nfunc AssertEqual[T comparable](t *testing.T, got, want T) {\n\tt.Helper()\n\tif got != w"
  },
  {
    "path": "generics/generics_test.go",
    "chars": 1154,
    "preview": "package generics\n\nimport \"testing\"\n\nfunc TestAssertFunctions(t *testing.T) {\n\tt.Run(\"asserting on integers\", func(t *tes"
  },
  {
    "path": "generics/stack.go",
    "chars": 452,
    "preview": "package generics\n\ntype Stack[T any] struct {\n\tvalues []T\n}\n\nfunc NewStack[T any]() *Stack[T] {\n\treturn new(Stack[T])\n}\n\n"
  },
  {
    "path": "generics.md",
    "chars": 24133,
    "preview": "# Generics\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main/ge"
  },
  {
    "path": "go.mod",
    "chars": 244,
    "preview": "module github.com/quii/learn-go-with-tests\n\ngo 1.24\n\nrequire (\n\tgithub.com/approvals/go-approval-tests v0.0.0-2021100813"
  },
  {
    "path": "go.sum",
    "chars": 661,
    "preview": "github.com/approvals/go-approval-tests v0.0.0-20211008131110-0c40b30e0000 h1:H152l3O+2XIXQu8IrqEXeqJOFCvSShUXs7+x0lw8V1k"
  },
  {
    "path": "hello-world/v1/hello.go",
    "chars": 73,
    "preview": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"Hello, world\")\n}\n"
  },
  {
    "path": "hello-world/v2/hello.go",
    "chars": 143,
    "preview": "package main\n\nimport \"fmt\"\n\n// Hello returns a greeting.\nfunc Hello() string {\n\treturn \"Hello, world\"\n}\n\nfunc main() {\n\t"
  },
  {
    "path": "hello-world/v2/hello_test.go",
    "chars": 167,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestHello(t *testing.T) {\n\tgot := Hello()\n\twant := \"Hello, world\"\n\n\tif got != want "
  },
  {
    "path": "hello-world/v3/hello.go",
    "chars": 176,
    "preview": "package main\n\nimport \"fmt\"\n\n// Hello returns a personalised greeting.\nfunc Hello(name string) string {\n\treturn \"Hello, \""
  },
  {
    "path": "hello-world/v3/hello_test.go",
    "chars": 174,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestHello(t *testing.T) {\n\tgot := Hello(\"Chris\")\n\twant := \"Hello, Chris\"\n\n\tif got !"
  },
  {
    "path": "hello-world/v4/hello.go",
    "chars": 223,
    "preview": "package main\n\nimport \"fmt\"\n\nconst englishHelloPrefix = \"Hello, \"\n\n// Hello returns a personalised greeting.\nfunc Hello(n"
  },
  {
    "path": "hello-world/v4/hello_test.go",
    "chars": 174,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestHello(t *testing.T) {\n\tgot := Hello(\"Chris\")\n\twant := \"Hello, Chris\"\n\n\tif got !"
  },
  {
    "path": "hello-world/v5/hello.go",
    "chars": 315,
    "preview": "package main\n\nimport \"fmt\"\n\nconst englishHelloPrefix = \"Hello, \"\n\n// Hello returns a personalised greeting, defaulting t"
  },
  {
    "path": "hello-world/v5/hello_test.go",
    "chars": 496,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestHello(t *testing.T) {\n\tt.Run(\"saying hello to people\", func(t *testing.T) {\n\t\tg"
  },
  {
    "path": "hello-world/v6/hello.go",
    "chars": 554,
    "preview": "package main\n\nimport \"fmt\"\n\nconst spanish = \"Spanish\"\nconst french = \"French\"\nconst englishHelloPrefix = \"Hello, \"\nconst"
  },
  {
    "path": "hello-world/v6/hello_test.go",
    "chars": 760,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestHello(t *testing.T) {\n\tt.Run(\"to a person\", func(t *testing.T) {\n\t\tgot := Hello"
  },
  {
    "path": "hello-world/v7/hello.go",
    "chars": 556,
    "preview": "package main\n\nimport \"fmt\"\n\nconst spanish = \"Spanish\"\nconst french = \"French\"\nconst englishHelloPrefix = \"Hello, \"\nconst"
  },
  {
    "path": "hello-world/v7/hello_test.go",
    "chars": 826,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestHello(t *testing.T) {\n\tt.Run(\"saying hello to people\", func(t *testing.T) {\n\t\tg"
  },
  {
    "path": "hello-world/v8/hello.go",
    "chars": 648,
    "preview": "package main\n\nimport \"fmt\"\n\nconst spanish = \"Spanish\"\nconst french = \"French\"\nconst englishHelloPrefix = \"Hello, \"\nconst"
  },
  {
    "path": "hello-world/v8/hello_test.go",
    "chars": 759,
    "preview": "package main\n\nimport \"testing\"\n\nfunc TestHello(t *testing.T) {\n\tt.Run(\"to a person\", func(t *testing.T) {\n\t\tgot := Hello"
  },
  {
    "path": "hello-world.md",
    "chars": 20921,
    "preview": "# Hello, World\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/mai"
  },
  {
    "path": "html-templates.md",
    "chars": 41076,
    "preview": "# HTML Templates\n\n**[You can find all the code here](https://github.com/quii/learn-go-with-tests/tree/main/blogrenderer)"
  },
  {
    "path": "http-handlers-revisited.md",
    "chars": 14898,
    "preview": "# Revisiting HTTP Handlers\n\n[**You can find all the code here**](https://github.com/quii/learn-go-with-tests/tree/main/q"
  },
  {
    "path": "http-server/v1/main.go",
    "chars": 154,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\thandler := http.HandlerFunc(PlayerServer)\n\tlog.Fatal(http.Li"
  },
  {
    "path": "http-server/v1/server.go",
    "chars": 188,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// PlayerServer currently returns \"20\" given _any_ request.\nfunc PlayerServ"
  },
  {
    "path": "http-server/v1/server_test.go",
    "chars": 408,
    "preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestGETPlayers(t *testing.T) {\n\trequest, _ :="
  },
  {
    "path": "http-server/v2/main.go",
    "chars": 390,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\n// InMemoryPlayerStore collects data about players in memory.\ntype InMemory"
  },
  {
    "path": "http-server/v2/server.go",
    "chars": 540,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about players.\ntype Play"
  },
  {
    "path": "http-server/v2/server_test.go",
    "chars": 1799,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\ntype StubPlayerStore struct {\n\tscores map[s"
  },
  {
    "path": "http-server/v3/main.go",
    "chars": 390,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\n// InMemoryPlayerStore collects data about players in memory.\ntype InMemory"
  },
  {
    "path": "http-server/v3/server.go",
    "chars": 823,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about players.\ntype Play"
  },
  {
    "path": "http-server/v3/server_test.go",
    "chars": 2188,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\ntype StubPlayerStore struct {\n\tscores map[s"
  },
  {
    "path": "http-server/v4/main.go",
    "chars": 489,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\n// InMemoryPlayerStore collects data about players in memory.\ntype InMemory"
  },
  {
    "path": "http-server/v4/server.go",
    "chars": 898,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about players.\ntype Play"
  },
  {
    "path": "http-server/v4/server_test.go",
    "chars": 2587,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\ntype StubPlayerStore struct {\n\tscores   map"
  },
  {
    "path": "http-server/v5/in_memory_player_store.go",
    "chars": 758,
    "preview": "package main\n\nimport \"sync\"\n\n// NewInMemoryPlayerStore initialises an empty player store.\nfunc NewInMemoryPlayerStore() "
  },
  {
    "path": "http-server/v5/main.go",
    "chars": 161,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tserver := &PlayerServer{NewInMemoryPlayerStore()}\n\tlog.Fatal"
  },
  {
    "path": "http-server/v5/server.go",
    "chars": 897,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about players.\ntype Play"
  },
  {
    "path": "http-server/v5/server_integration_test.go",
    "chars": 615,
    "preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestRecordingWinsAndRetrievingThem(t *testing"
  },
  {
    "path": "http-server/v5/server_test.go",
    "chars": 2686,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\ntype StubPlayerStore struct {\n\tscores   map"
  },
  {
    "path": "http-server.md",
    "chars": 35517,
    "preview": "# HTTP Server\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main"
  },
  {
    "path": "install-go.md",
    "chars": 3216,
    "preview": "# Install Go, set up environment for productivity\n\nThe official installation instructions for Go are available [here](ht"
  },
  {
    "path": "integers/v1/adder.go",
    "chars": 114,
    "preview": "package integers\n\n// Add takes two integers and returns the sum of them.\nfunc Add(x, y int) int {\n\treturn x + y\n}\n"
  },
  {
    "path": "integers/v1/adder_test.go",
    "chars": 184,
    "preview": "package integers\n\nimport \"testing\"\n\nfunc TestAdder(t *testing.T) {\n\tsum := Add(2, 2)\n\texpected := 4\n\n\tif sum != expected"
  },
  {
    "path": "integers/v2/adder.go",
    "chars": 114,
    "preview": "package integers\n\n// Add takes two integers and returns the sum of them.\nfunc Add(x, y int) int {\n\treturn x + y\n}\n"
  },
  {
    "path": "integers/v2/adder_test.go",
    "chars": 269,
    "preview": "package integers\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestAdder(t *testing.T) {\n\tsum := Add(2, 2)\n\texpected := 4\n\n\tif sum"
  },
  {
    "path": "integers.md",
    "chars": 7348,
    "preview": "# Integers\n\n**[You can find all the code for this chapter here](https://github.com/quii/learn-go-with-tests/tree/main/in"
  },
  {
    "path": "intro-to-acceptance-tests.md",
    "chars": 18615,
    "preview": "# Introduction to acceptance testing\n\nAt `$WORK`, we've been running into the need to have \"graceful shutdown\" for our s"
  },
  {
    "path": "io/v1/file_system_store.go",
    "chars": 348,
    "preview": "package main\n\nimport (\n\t\"io\"\n)\n\n// FileSystemPlayerStore stores players in the filesystem.\ntype FileSystemPlayerStore st"
  },
  {
    "path": "io/v1/file_system_store_test.go",
    "chars": 486,
    "preview": "package main\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestFileSystemStore(t *testing.T) {\n\n\tt.Run(\"league from a reader\","
  },
  {
    "path": "io/v1/in_memory_player_store.go",
    "chars": 780,
    "preview": "package main\n\n// NewInMemoryPlayerStore initialises an empty player store.\nfunc NewInMemoryPlayerStore() *InMemoryPlayer"
  },
  {
    "path": "io/v1/league.go",
    "chars": 228,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\n// NewLeague creates a league from JSON.\nfunc NewLeague(rdr io.Reader) "
  },
  {
    "path": "io/v1/main.go",
    "chars": 163,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tserver := NewPlayerServer(NewInMemoryPlayerStore())\n\tlog.Fat"
  },
  {
    "path": "io/v1/server.go",
    "chars": 1629,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about p"
  },
  {
    "path": "io/v1/server_integration_test.go",
    "chars": 969,
    "preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestRecordingWinsAndRetrievingThem(t *testing"
  },
  {
    "path": "io/v1/server_test.go",
    "chars": 3999,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype StubPlayerStore struc"
  },
  {
    "path": "io/v2/file_system_store.go",
    "chars": 598,
    "preview": "package main\n\nimport (\n\t\"io\"\n)\n\n// FileSystemPlayerStore stores players in the filesystem.\ntype FileSystemPlayerStore st"
  },
  {
    "path": "io/v2/file_system_store_test.go",
    "chars": 905,
    "preview": "package main\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestFileSystemStore(t *testing.T) {\n\n\tt.Run(\"league from a reader\","
  },
  {
    "path": "io/v2/in_memory_player_store.go",
    "chars": 780,
    "preview": "package main\n\n// NewInMemoryPlayerStore initialises an empty player store.\nfunc NewInMemoryPlayerStore() *InMemoryPlayer"
  },
  {
    "path": "io/v2/league.go",
    "chars": 311,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// NewLeague creates a league from JSON.\nfunc NewLeague(rdr io.R"
  },
  {
    "path": "io/v2/main.go",
    "chars": 163,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tserver := NewPlayerServer(NewInMemoryPlayerStore())\n\tlog.Fat"
  },
  {
    "path": "io/v2/server.go",
    "chars": 1629,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about p"
  },
  {
    "path": "io/v2/server_integration_test.go",
    "chars": 969,
    "preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestRecordingWinsAndRetrievingThem(t *testing"
  },
  {
    "path": "io/v2/server_test.go",
    "chars": 3999,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype StubPlayerStore struc"
  },
  {
    "path": "io/v3/file_system_store.go",
    "chars": 889,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\n// FileSystemPlayerStore stores players in the filesystem.\ntype FileSys"
  },
  {
    "path": "io/v3/file_system_store_test.go",
    "chars": 1705,
    "preview": "package main\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc createTempFile(t testing.TB, initialData string) (io.ReadWriteSeek"
  },
  {
    "path": "io/v3/in_memory_player_store.go",
    "chars": 778,
    "preview": "package main\n\n// NewInMemoryPlayerStore initialises an empty player store.\nfunc NewInMemoryPlayerStore() *InMemoryPlayer"
  },
  {
    "path": "io/v3/league.go",
    "chars": 548,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// League stores a collection of players.\ntype League []Player\n\n"
  },
  {
    "path": "io/v3/main.go",
    "chars": 163,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tserver := NewPlayerServer(NewInMemoryPlayerStore())\n\tlog.Fat"
  },
  {
    "path": "io/v3/server.go",
    "chars": 1627,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about p"
  },
  {
    "path": "io/v3/server_integration_test.go",
    "chars": 969,
    "preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestRecordingWinsAndRetrievingThem(t *testing"
  },
  {
    "path": "io/v3/server_test.go",
    "chars": 3997,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype StubPlayerStore struc"
  },
  {
    "path": "io/v4/file_system_store.go",
    "chars": 942,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\n// FileSystemPlayerStore stores players in the filesystem.\ntype FileSys"
  },
  {
    "path": "io/v4/file_system_store_test.go",
    "chars": 2071,
    "preview": "package main\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc createTempFile(t testing.TB, initialData string) (io.ReadWriteSeek"
  },
  {
    "path": "io/v4/in_memory_player_store.go",
    "chars": 778,
    "preview": "package main\n\n// NewInMemoryPlayerStore initialises an empty player store.\nfunc NewInMemoryPlayerStore() *InMemoryPlayer"
  },
  {
    "path": "io/v4/league.go",
    "chars": 548,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// League stores a collection of players.\ntype League []Player\n\n"
  },
  {
    "path": "io/v4/main.go",
    "chars": 163,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tserver := NewPlayerServer(NewInMemoryPlayerStore())\n\tlog.Fat"
  },
  {
    "path": "io/v4/server.go",
    "chars": 1627,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PlayerStore stores score information about p"
  },
  {
    "path": "io/v4/server_integration_test.go",
    "chars": 969,
    "preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestRecordingWinsAndRetrievingThem(t *testing"
  },
  {
    "path": "io/v4/server_test.go",
    "chars": 3997,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype StubPlayerStore struc"
  },
  {
    "path": "io/v5/file_system_store.go",
    "chars": 942,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\n// FileSystemPlayerStore stores players in the filesystem.\ntype FileSys"
  }
]

// ... and 399 more files (download for full content)

About this extraction

This page contains the full source code of the quii/learn-go-with-tests GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 599 files (1.5 MB), approximately 427.9k tokens, and a symbol index with 2425 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!