| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 1 | # Contributing to gophercloud |
| 2 | |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 3 | - [Getting started](#getting-started) |
| 4 | - [Tests](#tests) |
| 5 | - [Style guide](#basic-style-guide) |
| 6 | - [4 ways to get involved](#4-ways-to-get-involved) |
| 7 | |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 8 | ## Getting started |
| 9 | |
| 10 | There should be no fundamental differences of setup between contributors and |
| 11 | normal end-users. The only thing to bear in mind is that you will need to add a |
| 12 | few extra environment variables for acceptance tests - this is documented in |
| 13 | our [acceptance tests readme](/acceptance). |
| 14 | |
| 15 | ## Tests |
| 16 | |
| 17 | When working on a new or existing feature, testing will be the backbone of your |
| 18 | work since it helps uncover and prevent regressions in the codebase. There are |
| 19 | two types of test we use in gophercloud: unit tests and acceptance tests, which |
| 20 | are both described below. |
| 21 | |
| 22 | ### Unit tests |
| 23 | |
| 24 | Unit tests are the fine-grained tests that establish and ensure the behaviour |
| 25 | of individual units of functionality. We usually test on an |
| 26 | operation-by-operation basis (an operation typically being an API action) with |
| 27 | the use of mocking to set up explicit expectations. Each operation will set up |
| 28 | its HTTP response expectation, and then test how the system responds when fed |
| 29 | this controlled, pre-determined input. |
| 30 | |
| 31 | To make life easier, we've introduced a bunch of test helpers to simplify the |
| 32 | process of testing expectations with assertions: |
| 33 | |
| 34 | ```go |
| 35 | import ( |
| 36 | "testing" |
| 37 | |
| 38 | "github.com/rackspace/gophercloud/testhelper" |
| 39 | ) |
| 40 | |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 41 | func TestSomething(t *testing.T) { |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 42 | result, err := Operation() |
| 43 | |
| 44 | testhelper.AssertEquals(t, "foo", result.Bar) |
| 45 | testhelper.AssertNoErr(t, err) |
| 46 | } |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 47 | |
| 48 | func TestSomethingElse(t *testing.T) { |
| 49 | testhelper.CheckEquals(t, "expected", "actual") |
| 50 | } |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 51 | ``` |
| 52 | |
| 53 | `AssertEquals` and `AssertNoErr` will throw a fatal error if a value does not |
| 54 | match an expected value or if an error has been declared, respectively. You can |
| 55 | also use `CheckEquals` and `CheckNoErr` for the same purpose; the only difference |
| 56 | being that `t.Errorf` is raised rather than `t.Fatalf`. |
| 57 | |
| 58 | Here is a truncated example of mocked HTTP responses: |
| 59 | |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 60 | ```go |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 61 | import ( |
| 62 | "testing" |
| 63 | |
| 64 | th "github.com/rackspace/gophercloud/testhelper" |
| 65 | fake "github.com/rackspace/gophercloud/testhelper/client" |
| 66 | ) |
| 67 | |
| 68 | func TestGet(t *testing.T) { |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 69 | // Setup the HTTP request multiplexer and server |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 70 | th.SetupHTTP() |
| 71 | defer th.TeardownHTTP() |
| 72 | |
| 73 | th.Mux.HandleFunc("/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { |
| 74 | // Test we're using the correct HTTP method |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 75 | th.TestMethod(t, r, "GET") |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 76 | |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 77 | // Test we're setting the auth token |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 78 | th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) |
| 79 | |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 80 | // Set the appropriate headers for our mocked response |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 81 | w.Header().Add("Content-Type", "application/json") |
| 82 | w.WriteHeader(http.StatusOK) |
| 83 | |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 84 | // Set the HTTP body |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 85 | fmt.Fprintf(w, ` |
| 86 | { |
| 87 | "network": { |
| 88 | "status": "ACTIVE", |
| 89 | "name": "private-network", |
| 90 | "admin_state_up": true, |
| 91 | "tenant_id": "4fd44f30292945e481c7b8a0c8908869", |
| 92 | "shared": true, |
| 93 | "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22" |
| 94 | } |
| 95 | } |
| 96 | `) |
| 97 | }) |
| 98 | |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 99 | // Call our API operation |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 100 | network, err := Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() |
| 101 | |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 102 | // Assert no errors and equality |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 103 | th.AssertNoErr(t, err) |
| 104 | th.AssertEquals(t, n.Status, "ACTIVE") |
| 105 | } |
| 106 | ``` |
| 107 | |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 108 | ### Acceptance tests |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 109 | |
| 110 | As we've already mentioned, unit tests have a very narrow and confined focus - |
| 111 | they test small units of behaviour. Acceptance tests on the other hand have a |
| 112 | far larger scope: they are fully functional tests that test the entire API of a |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 113 | service in one fell swoop. They don't care about unit isolation or mocking |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 114 | expectations, they instead do a full run-through and consequently test how the |
| 115 | entire system _integrates_ together. When an API satisfies expectations, it |
| 116 | proves by default that the requirements for a contract have been met. |
| 117 | |
| 118 | ### Running tests |
| 119 | |
| 120 | To run all tests: |
| 121 | |
| 122 | ```bash |
| 123 | go test ./... |
| 124 | ``` |
| 125 | |
| 126 | To run all tests with verbose output: |
| 127 | |
| 128 | ```bash |
| 129 | go test -v ./... |
| 130 | ``` |
| 131 | |
| 132 | To run tests that match certain [build tags](): |
| 133 | |
| 134 | ```bash |
| 135 | go test -tags "foo bar" ./... |
| 136 | ``` |
| 137 | |
| 138 | To run tests for a particular sub-package: |
| 139 | |
| 140 | ```bash |
| 141 | cd ./path/to/package && go test . |
| 142 | ``` |
| 143 | |
| 144 | ## Basic style guide |
| 145 | |
| 146 | We follow the standard formatting recommendations and language idioms set out |
| 147 | in the [Effective Go](https://golang.org/doc/effective_go.html) guide. It's |
| 148 | definitely worth reading - but the relevant sections are |
| 149 | [formatting](https://golang.org/doc/effective_go.html#formatting) |
| 150 | and [names](https://golang.org/doc/effective_go.html#names). |
| 151 | |
| 152 | ## 4 ways to get involved |
| 153 | |
| 154 | There are four main ways you can get involved in our open-source project, and |
| 155 | each is described briefly below. Once you've made up your mind and decided on |
| 156 | your fix, you will need to follow the same basic steps that all submissions are |
| 157 | required to adhere to: |
| 158 | |
| 159 | 1. [fork](https://help.github.com/articles/fork-a-repo/) the `rackspace/gophercloud` repository |
| 160 | 2. checkout a [new branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches) |
| 161 | 3. ensure all your commits are [well-organized](https://help.github.com/articles/about-git-rebase/) |
| 162 | 4. submit your branch as a [pull request](https://help.github.com/articles/creating-a-pull-request/) |
| 163 | |
| 164 | ### 1. Fixing bugs |
| 165 | |
| 166 | If you want to start fixing open bugs, we'd really appreciate that! Bug fixing |
| 167 | is central to any project. The best way to get started is by heading to our |
| 168 | [bug tracker](https://github.com/rackspace/gophercloud/issues) and finding open |
| 169 | bugs that you think nobody is working on. It might be useful to comment on the |
| 170 | thread to see the current state of the issue and if anybody has made any |
| 171 | breakthroughs on it so far. |
| 172 | |
| 173 | ### 2. Improving documentation |
| 174 | |
| 175 | We have three forms of documentation: |
| 176 | |
| 177 | * short README documents that briefly introduce a topic |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 178 | * reference documentation on [godoc.org](http://godoc.org) that is automatically |
| 179 | generated from source code comments |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 180 | * user documentation on our [homepage](http://gophercloud.io) that includes |
| 181 | getting started guides, installation guides and code samples |
| 182 | |
| 183 | If you feel that a certain section could be improved - whether its to clarify |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 184 | ambiguity or fix a grammatical mistake - please feel entitled to do so! We |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 185 | welcome doc pull requests with the same childlike enthusiasm as any other |
| 186 | contribution! |
| 187 | |
| 188 | ### 3. Optimizing existing features |
| 189 | |
| 190 | If you would like to improve or optimize an existing feature, please be aware |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 191 | that we adhere to [semantic versioning](http://semver.org) - which means that |
| 192 | we cannot introduce breaking changes to the API without a major version change |
| 193 | (v1.x -> v2.x). Making that leap is a big step, so we encourage contributors to |
| 194 | refactor rather than rewrite. Running tests will prevent regression and avoid |
| 195 | the possibility of breaking somebody's current implementation. |
| Jamie Hannaford | d5a1cb7 | 2014-10-07 14:31:27 +0200 | [diff] [blame] | 196 | |
| 197 | ### 4. Working on a new feature |
| 198 | |
| 199 | If you've found something we've left out, definitely feel free to start work on |
| 200 | introducing that feature. It's always useful to open an issue first to indicate |
| 201 | your intent to a core contributor - this enables quick feedback and can help |
| 202 | steer you in the right direction by avoiding known issues. It might also help |
| 203 | you avoid losing time implementing something that might not ever work. |
| 204 | |
| 205 | You must ensure that all of your work is well tested - both in terms of unit |
| 206 | and acceptance tests. Untested code will not be merged because it introduces |
| Jamie Hannaford | d2b6dfc | 2014-10-07 14:46:55 +0200 | [diff] [blame^] | 207 | too much of a risk to end-users. |
| 208 | |
| 209 | Happy hacking! |