Testing

edit

Unit Tests

edit

For unit tests, use only the Go standard library testing package. To make comparing complex structures less verbose, we use the assert package from the testify library.

For parser and decoder tests, it’s a good practice to have an array with test cases containing the inputs and expected outputs. For an example, see the Test_splitCookiesHeaders unit test.

You can also have unit tests that treat the whole module as a black box, calling its interface functions, then reading the result from the results channel and checking it. This pattern is especially useful for checking corner cases related to packet boundaries or correlation issues. Here is an example from the HTTP module:

func Test_gap_in_body_http1dot0_fin(t *testing.T) {
	if testing.Verbose() { 
		logp.LogInit(logp.LOG_DEBUG, "", false, true, []string{"http",
			"httpdetailed"})
	}
	http := HttpModForTests()

	data1 := []byte("GET / HTTP/1.0\r\n\r\n") 

	data2 := []byte("HTTP/1.0 200 OK\r\n" +
		"Date: Tue, 14 Aug 2012 22:31:45 GMT\r\n" +
		"Expires: -1\r\n" +
		"Cache-Control: private, max-age=0\r\n" +
		"Content-Type: text/html; charset=UTF-8\r\n" +
		"Content-Encoding: gzip\r\n" +
		"Server: gws\r\n" +
		"X-XSS-Protection: 1; mode=block\r\n" +
		"X-Frame-Options: SAMEORIGIN\r\n" +
		"\r\n" +
		"xxxxxxxxxxxxxxxxxxxx")

	tcptuple := testTcpTuple()
	req := protos.Packet{Payload: data1}
	resp := protos.Packet{Payload: data2}

	private := protos.ProtocolData(new(httpPrivateData))

	private = http.Parse(&req, tcptuple, 0, private) 
	private = http.ReceivedFin(tcptuple, 0, private)

	private = http.Parse(&resp, tcptuple, 1, private)

	logp.Debug("http", "Now sending gap..")

	private, drop := http.GapInStream(tcptuple, 1, 10, private)
	assert.Equal(t, false, drop)

	private = http.ReceivedFin(tcptuple, 1, private)

	trans := expectTransaction(t, http) 
	assert.NotNil(t, trans)
	assert.Equal(t, trans["notes"], []string{"Packet loss while capturing the response"})
}

It’s useful to initialize the logging system in case the -v flag is passed to go test. This makes it easy to get the logs for a failing test while keeping the output clean on a normal run.

Define the data we’ll be using in the test.

Call the interface functions exported by the module. The private structure is passed from one call to the next like the TCP layer would do.

The expectTransaction function tries to read from the results channel and causes errors in the test case if there’s no transaction present.

To check the coverage of your unit tests, run the make cover command at the top of the repository.

System Testing

edit

Because the main input to Packetbeat are packets and the main output are JSON objects, a convenient way of testing its functionality is by providing PCAP files as input and checking the results in the files created by using the "file" output plugin.

This is the approach taken by the tests in the tests/system directory. The tests are written in Python and executed using nose. Here is a simple example test from the MongoDB suite:

    def test_mongodb_find(self):
        """
        Should correctly pass a simple MongoDB find query
        """
        self.render_config_template( 
            mongodb_ports=[27017]
        )
        self.run_packetbeat(pcap="mongodb_find.pcap", 
                            debug_selectors=["mongodb"])

        objs = self.read_output() 
        o = objs[0]
        assert o["type"] == "mongodb"
        assert o["method"] == "find"
        assert o["status"] == "OK"

The configuration file for each test run is generated from the template. If your protocol plugin has options in the configuration file, you should add them to the template.

The run_packetbeat function receives the PCAP file to run. It looks for the PCAP file in the tests/pcaps folder. The debug_selectors array controls which log lines to be included. You can use debug_selectors=["*"] to enable all debug messages.

After the run, the test reads the output files and checks the result.

Tip: To generate the PCAP files, you can use Packetbeat. The -dump CLI flag will dump to disk all the packets sniffed from the network that match the BPF filter.

To run the whole test suite, use:

$ make test

This requires you to have Python and virtualenv installed, but it automatically creates and uses the virtualenv.

To run an individual test, use the following steps:

$ cd tests
$ . env/bin/activate
$ nosetests test_0025_mongodb_basic.py:Test.test_write_errors

After running the individual test, you can check the logs, the output, and the configuration file manually by looking into the folder that the last_run symlink points to:

$ cd last_run
$ ls
output packetbeat.log packetbeat.yml