github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/CONTRIBUTING.MD (about) 1 2 # Hacking on JX 3 4 This guide is for developers who want to improve the Jenkins X jx CLI. These instructions will help you set up a 5 development environment for working on the jx source code. 6 7 ## Prerequisites 8 9 To compile, test and contribute towards the jx binaries you will need: 10 11 - [git][] 12 - [Go][] 1.11, 1.12 or 1.13, with support for compiling to `linux/amd64` (Go version 1.14 is at the moment not supported). 13 - [dep](https://github.com/golang/dep) 14 - [pre-commit](https://pre-commit.com) _optional: we use [detect-secrets](https://github.com/Yelp/detect-secrets) to help prevent secrets leaking into the code base_ 15 16 17 In most cases, install the prerequisite according to its instructions. See the next section 18 for a note about Go cross-compiling support. 19 20 ### Configuring Go 21 22 The jx's binary eCLI is built on your machine in your GO Path. 23 24 On macOS, Go can be installed with [Homebrew][]: 25 26 ```shell 27 28 $ brew install go 29 ``` 30 31 It is also straightforward to build Go from source: 32 33 ```shell 34 $ sudo su 35 $ curl -sSL https://storage.googleapis.com/golang/go1.7.5.src.tar.gz | tar -C /usr/local -xz 36 $ cd /usr/local/go/src 37 $ # compile Go for the default platform first, then add cross-compile support 38 $ ./make.bash --no-clean 39 $ GOOS=linux GOARCH=amd64 ./make.bash --no-clean 40 ``` 41 42 ## Fork the Repository 43 44 Begin at Github by forking jx, then clone your fork locally. Since jx is a Go package, it 45 should be located at `$GOPATH/src/github.com/jenkins-x/jx`. 46 47 ```shell 48 $ mkdir -p $GOPATH/src/github.com/jenkins-x 49 $ cd $GOPATH/src/github.com/jenkins-x 50 $ git clone git@github.com:<username>/jx.git 51 $ cd jx 52 ``` 53 54 Add the conventional [upstream][] `git` remote in order to fetch changes from jx's main master 55 branch and to create pull requests: 56 57 ```shell 58 $ git remote add upstream https://github.com/jenkins-x/jx.git 59 ``` 60 61 ## Build Your Changes 62 63 With the prerequisites installed and your fork of jx cloned, you can make changes to local jx 64 source code. 65 66 Run `make` to build the `jx` binaries: 67 68 ```shell 69 70 $ make build # runs dep and builds `jx` inside the build/ 71 ``` 72 73 ## Testing 74 75 The jx test suite is divided into three sections: 76 - The standard unit test suite 77 - Slow unit tests 78 - Integration tests 79 80 To run the standard test suite: 81 ```make test``` 82 83 To run the standard test suite including slow running tests: 84 ```make test-slow``` 85 86 To run all tests including integration tests (NOTE These tests are not encapsulated): 87 ```make test-slow-integration``` 88 89 90 To get a nice HTML report on the tests: 91 ```make test-report-html``` 92 93 ### Writing tests 94 95 ### Unit Tests 96 97 Unit tests should be isolated (see what is an unencapsulated test), and should contain the `t.Parallel()` directive in order to keep things nice and speedy. 98 99 If you add a slow running (more than a couple of seconds) test, it needs to be wrapped like so: 100 ``` 101 if testing.Short() { 102 t.Skip("skipping a_long_running_test") 103 } else { 104 // Slow test goes here... 105 } 106 ``` 107 Slows tests can (and should) still include `t.Parallel()` 108 109 Best practice for unit tests is to define the testing package appending _test to the name of your package, e.g. `mypackage_test` and then import `mypackage` inside your tests. 110 This encourages good package design and will enable you to define the exported package API in a composable way. 111 112 ### Integration Tests 113 114 To add an integration test, create a separate file for your integration tests using the naming convention `mypackage_integration_test.go` Use the same package declaration as your unit tests: `mypackage_test`. At the very top of the file before the package declaration add this custom build directive: 115 116 ``` 117 // +build integration 118 119 ``` 120 Note that there needs to be a blank line before you declare the package name. 121 122 This directive will ensure that integration tests are automatically separated from unit tests, and will not be run as part of the normal test suite. 123 You should NOT add `t.Parallel()` to an unencapsulated test as it may cause intermittent failures. 124 125 ### What is an unencapsulated test? 126 A test is unencapsulated (not isolated) if it cannot be run (with repeatable success) without a certain surrounding state. Relying on external binaries that may not be present, writing or reading from the filesystem without care to specifically avoid collisions, or relying on other tests to run in a specific sequence for your test to pass are all examples of a test that you should carefully consider before committing. If you would like to easily check that your test is isolated before committing simply run: `make docker-test`, or if your test is marked as slow: `make docker-test-slow`. This will mount the jx project folder into a golang docker container that does not include any of your host machines environment. If your test passes here, then you can be happy that the test is encapsulated. 127 128 ### Mocking / Stubbing 129 Mocking or stubbing methods in your unit tests will get you a long way towards test isolation. Coupled with the use of interface based APIs you should be able to make your methods easily testable and useful to other packages that may need to import them. 130 https://github.com/petergtz/pegomock Is our current mocking library of choice, mainly because it is very easy to use and doesn't require you to write your own mocks (Yay!) 131 We place all interfaces for each package in a file called `interface.go` in the relevant folder. So you can find all interfaces for `github.com/jenkins-x/jx/v2/pkg/util` in `github.com/jenkins-x/jx/v2/pkg/util/interface.go` 132 Generating/Regenerating a mock for a given interface is easy, just go to the `interface.go` file that corresponds with the interface you would like to mock and add a comment directly above your interface definition that will look something like this: 133 ``` 134 // CommandInterface defines the interface for a Command 135 //go:generate pegomock generate github.com/jenkins-x/jx/v2/pkg/util CommandInterface -o mocks/command_interface.go 136 type CommandInterface interface { 137 DidError() bool 138 DidFail() bool 139 Error() error 140 Run() (string, error) 141 RunWithoutRetry() (string, error) 142 SetName(string) 143 SetDir(string) 144 SetArgs([]string) 145 SetTimeout(time.Duration) 146 SetExponentialBackOff(*backoff.ExponentialBackOff) 147 } 148 ``` 149 In the example you can see that we pass the generator to use: `pegomock generate` the package path name: `github.com/jenkins-x/jx/v2/pkg/util` the name of the interface: `CommandInterface` and finally an output directive to write the generated file to a mock subfolder. To keep things nice and tidy it's best to write each mocked interface to a separate file in this folder. So in this case: `-o mocks/command_interface.go` 150 151 Now simply run: 152 ``` 153 go generate ./... 154 ``` 155 or 156 ``` 157 make generate 158 ``` 159 160 You now have a mock to test your new interface! 161 The new mock can now be imported into your test file and used for easy mocking/stubbing. 162 Here's an example: 163 ``` 164 package util_test 165 166 import ( 167 "errors" 168 "testing" 169 170 "github.com/jenkins-x/jx/v2/pkg/util" 171 mocks "github.com/jenkins-x/jx/v2/pkg/util/mocks" 172 . "github.com/petergtz/pegomock" 173 "github.com/stretchr/testify/assert" 174 ) 175 176 func TestJXBinaryLocationSuccess(t *testing.T) { 177 t.Parallel() 178 commandInterface := mocks.NewMockCommandInterface() 179 When(commandInterface.RunWithoutRetry()).ThenReturn("/test/something/bin/jx", nil) 180 181 res, err := util.JXBinaryLocation(commandInterface) 182 assert.Equal(t, "/test/something/bin", res) 183 assert.NoError(t, err, "Should not error") 184 } 185 ``` 186 Here we're importing the mock we need in our import declaration: 187 ``` 188 mocks "github.com/jenkins-x/jx/v2/pkg/util/mocks" 189 ``` 190 Then inside the test we're instantiating `NewMockCommandInterface` which was automatically generated for us by pegomock. 191 192 Next we're stubbing something that we don't actually want to run when we execute our test. In this case we don't want to make a call to an external binary as that could break our tests isolation. We're using some handy matchers which are provided by pegomock, and importing using a `.` import to keep the syntax neat (You probably shouldn't do this outside of tests): 193 ``` 194 When(commandInterface.RunWithoutRetry()).ThenReturn("/test/something/bin/jx", nil) 195 ``` 196 Now when we can setup our test using the mock interface and make assertions as normal. 197 198 199 ### Debug logging 200 201 Lots of the test have debug output to try figure out when things fail. You can enable verbose debug logging for tests via 202 203 ```shell 204 export JX_TEST_DEBUG=true 205 ``` 206 207 ## Debugging 208 209 First you need to [install Delve](https://github.com/derekparker/delve/blob/master/Documentation/installation/README.md) 210 211 Then you should be able to run a debug version of a jx command: 212 213 ``` 214 dlv --listen=:2345 --headless=true --api-version=2 exec ./build/jx -- some arguments 215 ``` 216 217 Then in you IDE you should be able to then set a breakpoint and connect to `2345`. 218 219 e.g. in IntellJ you create a new `Go Remote` execution and then hit `Debug` 220 221 ### Debugging jx with stdin 222 223 If you want to debug using `jx` with `stdin` to test out terminal interaction, you can start `jx` as usual from the command line then: 224 225 * find the `pid` of the jx command via something like `ps -elaf | grep jx` 226 * start Delve attaching to the pid: 227 228 ```shell 229 230 dlv --listen=:2345 --headless=true --api-version=2 attach SomePID 231 ``` 232 233 ### Debugging a unit test 234 235 You can run a single unit test via 236 237 ```shell 238 export TEST="TestSomething" 239 make test1 240 ``` 241 242 You can then start a Delve debug session on a unit test via: 243 244 ```shell 245 export TEST="TestSomething" 246 make debugtest1 247 ``` 248 249 Then set breakpoints and debug in your IDE like in the above debugging. 250 251 ### Using a helper script 252 253 If you create a bash file called `jxDebug` as the following (replacing `SomePid` with the actual `pid`): 254 255 ```bash 256 #!/bin/sh 257 echo "Debugging jx" 258 dlv --listen=:2345 --headless=true --api-version=2 exec `which jx` -- $* 259 ``` 260 261 Then you can change your `jx someArgs` CLI to `jxDebug someArgs` then debug it! 262 263 ## Pre-commit Hooks 264 265 These are installed as a git 'pre-commit' hook and it operates automatically via a hook when using the `git commit` command. To setup this hook: 266 * Install [pre-commit](https://pre-commit.com/#install) 267 * Once installed, ensure you're at the root of this repository where the `.pre-commit-config.yaml` file exists, then: 268 269 ```bash 270 pre-commit install 271 ``` 272 273 If you wish to find out more: 274 - [pre-commit](https://pre-commit.com) 275 - [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) 276 277 [git]: https://git-scm.com/ 278 [dep]: https://github.com/golang/dep 279 [go]: https://golang.org/ 280 [Homebrew]: https://brew.sh/ 281 [Kubernetes]: https://github.com/kubernetes/kubernetes 282 [upstream]: https://help.github.com/articles/fork-a-repo/ 283 [upx]: https://upx.github.io