github.com/jenkins-x/jx/v2@v2.1.155/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