hs-test: transition to ginkgo test framework 17/40517/13
authorAdrian Villin <avillin@cisco.com>
Thu, 14 Mar 2024 15:42:55 +0000 (11:42 -0400)
committerAdrian Villin <avillin@cisco.com>
Fri, 19 Apr 2024 14:52:05 +0000 (14:52 +0000)
Type: test

Change-Id: Ia38bf5549d20b22876f6082085b69a52a03d0142
Signed-off-by: Adrian Villin <avillin@cisco.com>
Signed-off-by: Matus Fabian <matfabia@cisco.com>
25 files changed:
extras/hs-test/Makefile
extras/hs-test/README.rst
extras/hs-test/address_allocator.go
extras/hs-test/container.go
extras/hs-test/cpu.go
extras/hs-test/docker/Dockerfile.vpp
extras/hs-test/echo_test.go
extras/hs-test/framework_test.go
extras/hs-test/go.mod
extras/hs-test/go.sum
extras/hs-test/hst_suite.go
extras/hs-test/http_test.go
extras/hs-test/ldp_test.go
extras/hs-test/linux_iperf_test.go
extras/hs-test/mirroring_test.go
extras/hs-test/proxy_test.go
extras/hs-test/raw_session_test.go
extras/hs-test/suite_nginx_test.go
extras/hs-test/suite_no_topo_test.go
extras/hs-test/suite_ns_test.go
extras/hs-test/suite_tap_test.go
extras/hs-test/suite_veth_test.go
extras/hs-test/test
extras/hs-test/vcl_test.go
extras/hs-test/vppinstance.go

index f0ec755..9c4d345 100644 (file)
@@ -23,6 +23,10 @@ ifeq ($(CPUS),)
 CPUS=1
 endif
 
+ifeq ($(PARALLEL),)
+PARALLEL=1
+endif
+
 ifeq ($(VPPSRC),)
 VPPSRC=$(shell pwd)/../..
 endif
@@ -35,8 +39,8 @@ ifeq ($(ARCH),)
 ARCH=$(shell dpkg --print-architecture)
 endif
 
-list_tests = @(grep -r ') Test' *_test.go | cut -d '*' -f2 | cut -d '(' -f1 | \
-               tr -d ' ' | tr ')' '/' | sed 's/Suite//')
+list_tests = @go run github.com/onsi/ginkgo/v2/ginkgo --dry-run -v --no-color --seed=2 | head -n -1 | grep 'Test' | \
+               sed 's/^/* /; s/\(Suite\) /\1\//g'
 
 .PHONY: help
 help:
@@ -60,6 +64,7 @@ help:
        @echo " TEST=[test-name]         - specific test to run"
        @echo " CPUS=[n-cpus]            - number of cpus to run with vpp"
        @echo " VPPSRC=[path-to-vpp-src] - path to vpp source files (for gdb)"
+       @echo " PARALLEL=[n-cpus]"               - number of test processes to spawn to run in parallel
        @echo
        @echo "List of all tests:"
        $(call list_tests)
@@ -78,7 +83,7 @@ build-vpp-debug:
 test: .deps.ok .build.vpp
        @bash ./test --persist=$(PERSIST) --verbose=$(VERBOSE) \
                --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \
-               --vppsrc=$(VPPSRC)
+               --vppsrc=$(VPPSRC) --parallel=$(PARALLEL)
 
 build-go:
        go build ./tools/http_server
index 6db832b..1dc1039 100644 (file)
@@ -9,7 +9,10 @@ End-to-end tests often want multiple VPP instances, network namespaces, differen
 and to execute external tools or commands. With such requirements the existing VPP test framework is not sufficient.
 For this, ``Go`` was chosen as a high level language, allowing rapid development, with ``Docker`` and ``ip`` being the tools for creating required topology.
 
-Go's package `testing`_ together with `go test`_ command form the base framework upon which the *hs-test* is built and run.
+`Ginkgo`_ forms the base framework upon which the *hs-test* is built and run.
+All tests are technically in a single suite because we are only using ``package main``. We simulate suite behavior by grouping tests by the topology they require.
+This allows us to run those mentioned groups in parallel, but not individual tests in parallel.
+
 
 Anatomy of a test case
 ----------------------
@@ -24,15 +27,16 @@ Anatomy of a test case
 **Action flow when running a test case**:
 
 #. It starts with running ``make test``. Optional arguments are VERBOSE, PERSIST (topology configuration isn't cleaned up after test run),
-   and TEST=<test-name> to run specific test.
-#. ``make list-tests`` (or ``make help``) shows all test names.
-#. ``go test`` compiles package ``main`` along with any files with names matching the file pattern ``*_test.go``
-   and then runs the resulting test binaries
-#. The go test framework runs each function matching :ref:`naming convention<test-convention>`. Each of these corresponds to a `test suite`_
-#. Testify toolkit's ``suite.Run(t *testing.T, suite TestingSuite)`` function runs the suite and does the following:
-
+   TEST=<test-name> to run a specific test and PARALLEL=[n-cpus].
+#. ``make list-tests`` (or ``make help``) shows all tests. The current `list of tests`_ is at the bottom of this document.
+#. ``Ginkgo`` looks for a spec suite in the current directory and then compiles it to a .test binary
+#. The Ginkgo test framework runs each function that was registered manually using ``registerMySuiteTest(s *MySuite)``. Each of these functions correspond to a suite
+#. Ginkgo's ``RunSpecs(t, "Suite description")`` function is the entry point and does the following:
+
+  #. Ginkgo compiles the spec, builds a spec tree
+  #. ``Describe`` container nodes in suite\_\*_test.go files are run (in series by default, or in parallel with the argument PARALLEL=[n-cpus])
   #. Suite is initialized. The topology is loaded and configured in this step
-  #. Test suite runs all the tests attached to it
+  #. Registered tests are run in generated ``It`` subject nodes
   #. Execute tear-down functions, which currently consists of stopping running containers
      and clean-up of test topology
 
@@ -43,23 +47,25 @@ This describes adding a new test case to an existing suite.
 For adding a new suite, please see `Modifying the framework`_ below.
 
 #. To write a new test case, create a file whose name ends with ``_test.go`` or pick one that already exists
-#. Declare method whose name starts with ``Test`` and specifies its receiver as a pointer to the suite's struct (defined in ``framework_test.go``)
+#. Declare method whose name ends with ``Test`` and specifies its parameter as a pointer to the suite's struct (defined in ``suite_*_test.go``)
 #. Implement test behaviour inside the test method. This typically includes the following:
 
-  #. Retrieve a running container in which to run some action. Method ``getContainerByName``
-     from ``HstSuite`` struct serves this purpose
-  #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``vppctl`` method to access debug CLI
-  #. Run arbitrary commands inside the containers with ``exec`` method
-  #. Run other external tool with one of the preexisting functions in the ``utils.go`` file.
-     For example, use ``wget`` with ``startWget`` function
-  #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else
-  #. Verify results of your tests using ``assert`` methods provided by the test suite,
-     implemented by HstSuite struct
+   #. Retrieve a running container in which to run some action. Method ``getContainerByName``
+      from ``HstSuite`` struct serves this purpose
+   #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``vppctl`` method to access debug CLI
+   #. Run arbitrary commands inside the containers with ``exec`` method
+   #. Run other external tool with one of the preexisting functions in the ``utils.go`` file.
+      For example, use ``wget`` with ``startWget`` function
+   #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else
+   #. Verify results of your tests using ``assert`` methods provided by the test suite, implemented by HstSuite struct or use ``Gomega`` assert functions.
+
+#. Create an ``init()`` function and register the test using ``register*SuiteTests(testCaseFunction)``
+
 
 **Example test case**
 
 Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other.
-This can be put in file ``extras/hs-test/my_test.go`` and run with command ``./test -run TestMySuite/TestMyCase``.
+This can be put in file ``extras/hs-test/my_test.go`` and run with command ``make test TEST=MyTest`` or ``ginkgo -v --trace --focus MyTest``.
 
 ::
 
@@ -69,7 +75,11 @@ This can be put in file ``extras/hs-test/my_test.go`` and run with command ``./t
                 "fmt"
         )
 
-        func (s *MySuite) TestMyCase() {
+        func init(){
+                registerMySuiteTest(MyTest)
+        }
+
+        func MyTest(s *MySuite) {
                 clientVpp := s.getContainerByName("client-vpp").vppInstance
 
                 serverVethAddress := s.netInterfaces["server-iface"].AddressString()
@@ -86,8 +96,7 @@ Modifying the framework
 
 .. _test-convention:
 
-#. Adding a new suite takes place in ``framework_test.go`` and by creating a new file for the suite.
-   Naming convention for the suite files is ``suite_name_test.go`` where *name* will be replaced
+#. To add a new suite, create a new file. Naming convention for the suite files is ``suite_name_test.go`` where *name* will be replaced
    by the actual name
 
 #. Make a ``struct``, in the suite file, with at least ``HstSuite`` struct as its member.
@@ -99,7 +108,17 @@ Modifying the framework
                         HstSuite
                 }
 
-#. In suite file, implement ``SetupSuite`` method which testify runs once before starting any of the tests.
+#. Create a new slice that will contain test functions with a pointer to the suite's struct: ``var myTests = []func(s *MySuite){}``
+
+#. Then create a new function that will append test functions to that slice:
+
+        ::
+
+                func registerMySuiteTests(tests ...func(s *MySuite)) {
+                       nginxTests = append(myTests, tests...)
+                }
+
+#. In suite file, implement ``SetupSuite`` method which Ginkgo runs once before starting any of the tests.
    It's important here to call ``configureNetworkTopology`` method,
    pass the topology name to the function in a form of file name of one of the *yaml* files in ``topo-network`` folder.
    Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml``
@@ -111,6 +130,8 @@ Modifying the framework
         ::
 
                 func (s *MySuite) SetupSuite() {
+                        s.HstSuite.SetupSuite()
+
                         // Add custom setup code here
 
                         s.configureNetworkTopology("myTopology")
@@ -123,19 +144,62 @@ Modifying the framework
         ::
 
                 func (s *MySuite) SetupTest() {
+                        s.HstSuite.setupTest()
                         s.SetupVolumes()
                         s.SetupContainers()
                 }
 
-#. In order for ``go test`` to run this suite, we need to create a normal test function and pass our suite to ``suite.Run``.
-   These functions are placed at the end of ``framework_test.go``
+#. In order for ``Ginkgo`` to run this suite, we need to create a ``Describe`` container node with setup nodes and an ``It`` subject node.
+   Place them at the end of the suite file
+
+   * Declare a suite struct variable before anything else
+   * To use ``BeforeAll()`` and ``AfterAll()``, the container has to be marked as ``Ordered``
+   * Because the container is now marked as Ordered, if a test fails, all the subsequent tests are skipped.
+     To override this behavior, decorate the container node with ``ContinueOnFailure``
 
         ::
 
-                func TestMySuite(t *testing.T) {
-                        var m MySuite
-                        suite.Run(t, &m)
-                }
+                var _ = Describe("MySuite", Ordered, ContinueOnFailure, func() {
+               var s MySuite
+               BeforeAll(func() {
+                       s.SetupSuite()
+               })
+               BeforeEach(func() {
+                       s.SetupTest()
+               })
+               AfterAll(func() {
+                       s.TearDownSuite()
+               })
+               AfterEach(func() {
+                       s.TearDownTest()
+               })
+               for _, test := range mySuiteTests {
+                       test := test
+                       pc := reflect.ValueOf(test).Pointer()
+                       funcValue := runtime.FuncForPC(pc)
+                       It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) {
+                               test(&s)
+                       }, SpecTimeout(time.Minute*5))
+               }
+                })
+
+#. Notice the loop - it will generate multiple ``It`` nodes, each running a different test.
+   ``test := test`` is necessary, otherwise only the last test in a suite will run.
+   For a more detailed description, check Ginkgo's documentation: https://onsi.github.io/ginkgo/#dynamically-generating-specs\.
+
+#. ``funcValue.Name()`` returns the full name of a function (e.g. ``fd.io/hs-test.MyTest``), however, we only need the test name (``MyTest``).
+
+#. To run certain tests solo, create a new slice that will only contain tests that have to run solo and a new register function.
+   Add a ``Serial`` decorator to the container node and ``Label("SOLO")`` to the ``It`` subject node:
+
+        ::
+
+                var _ = Describe("MySuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+                        ...
+                        It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) {
+                       test(&s)
+                       }, SpecTimeout(time.Minute*5))
+                })
 
 #. Next step is to add test cases to the suite. For that, see section `Adding a test case`_ above
 
@@ -186,14 +250,9 @@ Alternatively copy the executable from host system to the Docker image, similarl
 **Skipping tests**
 
 ``HstSuite`` provides several methods that can be called in tests for skipping it conditionally or unconditionally such as:
-``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``.
+``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``. You can also use Ginkgo's ``Skip()``.
 However the tests currently run under test suites which set up topology and containers before actual test is run. For the reason of saving
-test run time it is not advisable to use aforementioned skip methods and instead prefix test name with ``Skip``:
-
-::
-
-    func (s *MySuite) SkipTest(){
-
+test run time it is not advisable to use aforementioned skip methods and instead, just don't register the test.
 
 **Debugging a test**
 
@@ -201,11 +260,11 @@ It is possible to debug VPP by attaching ``gdb`` before test execution by adding
 
 ::
 
-    $ make test TEST=TestVeths/TestLDPreloadIperfVpp DEBUG=true
+    $ make test TEST=LDPreloadIperfVppTest DEBUG=true
     ...
     run following command in different terminal:
-    docker exec -it server-vpp gdb -ex "attach $(docker exec server-vpp pidof vpp)"
-    Afterwards press CTRL+C to continue
+    docker exec -it server-vpp2456109 gdb -ex "attach $(docker exec server-vpp2456109 pidof vpp)"
+    Afterwards press CTRL+\ to continue
 
 If a test consists of more VPP instances then this is done for each of them.
 
@@ -223,8 +282,38 @@ Generally, these will be updated on a per-need basis, for example when a bug is
 or a new version incompatibility issue occurs.
 
 
-.. _testing: https://pkg.go.dev/testing
-.. _go test: https://pkg.go.dev/cmd/go#hdr-Test_packages
-.. _test suite: https://github.com/stretchr/testify#suite-package
+.. _ginkgo: https://onsi.github.io/ginkgo/
 .. _volumes: https://docs.docker.com/storage/volumes/
 
+**List of tests**
+
+.. _list of tests:
+
+Please update this list whenever you add a new test by pasting the output below.
+
+* NsSuite/HttpTpsTest
+* NsSuite/VppProxyHttpTcpTest
+* NsSuite/VppProxyHttpTlsTest
+* NsSuite/EnvoyProxyHttpTcpTest
+* NginxSuite/MirroringTest
+* VethsSuiteSolo TcpWithLossTest [SOLO]
+* NoTopoSuiteSolo HttpStaticPromTest [SOLO]
+* TapSuite/LinuxIperfTest
+* NoTopoSuite/NginxHttp3Test
+* NoTopoSuite/NginxAsServerTest
+* NoTopoSuite/NginxPerfCpsTest
+* NoTopoSuite/NginxPerfRpsTest
+* NoTopoSuite/NginxPerfWrkTest
+* VethsSuite/EchoBuiltinTest
+* VethsSuite/HttpCliTest
+* VethsSuite/LDPreloadIperfVppTest
+* VethsSuite/VppEchoQuicTest
+* VethsSuite/VppEchoTcpTest
+* VethsSuite/VppEchoUdpTest
+* VethsSuite/XEchoVclClientUdpTest
+* VethsSuite/XEchoVclClientTcpTest
+* VethsSuite/XEchoVclServerUdpTest
+* VethsSuite/XEchoVclServerTcpTest
+* VethsSuite/VclEchoTcpTest
+* VethsSuite/VclEchoUdpTest
+* VethsSuite/VclRetryAttachTest
index 72bc298..e05ea76 100644 (file)
@@ -12,7 +12,7 @@ import (
 type AddressCounter = int
 
 type Ip4AddressAllocator struct {
-       networks map[int]AddressCounter
+       networks    map[int]AddressCounter
        chosenOctet int
        assignedIps []string
 }
@@ -47,7 +47,7 @@ func (a *Ip4AddressAllocator) NewIp4InterfaceAddress(inputNetworkNumber ...int)
 // Creates a file every time an IP is assigned: used to keep track of addresses in use.
 // If an address is not in use, 'counter' is then copied to 'chosenOctet' and it is used for the remaining tests.
 // Also checks host IP addresses.
-func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddresses int) (string, error){
+func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddresses int) (string, error) {
        hostIps, _ := exechelper.CombinedOutput("ip a")
        counter := 10
        var address string
@@ -56,7 +56,7 @@ func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddress
                if a.chosenOctet != 0 {
                        address = fmt.Sprintf("10.%v.%v.%v", a.chosenOctet, networkNumber, numberOfAddresses)
                        file, err := os.Create(address)
-                       if err != nil{
+                       if err != nil {
                                return "", errors.New("unable to create file: " + fmt.Sprint(err))
                        }
                        file.Close()
@@ -68,14 +68,14 @@ func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddress
                                counter++
                        } else if os.IsNotExist(err) {
                                file, err := os.Create(address)
-                                       if err != nil{
+                               if err != nil {
                                        return "", errors.New("unable to create file: " + fmt.Sprint(err))
-                                       }
+                               }
                                file.Close()
                                a.chosenOctet = counter
                                break
                        } else {
-                               return "", errors.New("an error occured while checking if a file exists: " + fmt.Sprint(err))
+                               return "", errors.New("an error occurred while checking if a file exists: " + fmt.Sprint(err))
                        }
                }
        }
@@ -84,8 +84,8 @@ func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddress
        return address, nil
 }
 
-func (a *Ip4AddressAllocator) deleteIpAddresses(){
-       for ip := range a.assignedIps{
+func (a *Ip4AddressAllocator) deleteIpAddresses() {
+       for ip := range a.assignedIps {
                os.Remove(a.assignedIps[ip])
        }
 }
index 87e8aa3..c82f1fc 100644 (file)
@@ -9,6 +9,7 @@ import (
        "time"
 
        "github.com/edwarnicke/exechelper"
+       . "github.com/onsi/ginkgo/v2"
 )
 
 const (
@@ -38,7 +39,7 @@ type Container struct {
        vppInstance      *VppInstance
 }
 
-func newContainer(suite *HstSuite, yamlInput ContainerConfig, pid string) (*Container, error) {
+func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) {
        containerName := yamlInput["name"].(string)
        if len(containerName) == 0 {
                err := fmt.Errorf("container name must not be blank")
@@ -48,7 +49,7 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig, pid string) (*Cont
        var container = new(Container)
        container.volumes = make(map[string]Volume)
        container.envVars = make(map[string]string)
-       container.name = containerName + pid
+       container.name = containerName
        container.suite = suite
 
        if image, ok := yamlInput["image"]; ok {
@@ -76,7 +77,7 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig, pid string) (*Cont
        }
 
        if _, ok := yamlInput["volumes"]; ok {
-               workingVolumeDir := logDir + container.suite.T().Name() + pid + volumeDir
+               workingVolumeDir := logDir + CurrentSpecReport().LeafNodeText + container.suite.pid + volumeDir
                workDirReplacer := strings.NewReplacer("$HST_DIR", workDir)
                volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
                for _, volu := range yamlInput["volumes"].([]interface{}) {
@@ -249,7 +250,7 @@ func (c *Container) copy(sourceFileName string, targetFileName string) error {
 }
 
 func (c *Container) createFile(destFileName string, content string) error {
-       f, err := os.CreateTemp("/tmp", "hst-config" + c.suite.pid)
+       f, err := os.CreateTemp("/tmp", "hst-config"+c.suite.pid)
        if err != nil {
                return err
        }
@@ -273,7 +274,7 @@ func (c *Container) execServer(command string, arguments ...any) {
        serverCommand := fmt.Sprintf(command, arguments...)
        containerExecCommand := "docker exec -d" + c.getEnvVarsAsCliOption() +
                " " + c.name + " " + serverCommand
-       c.suite.T().Helper()
+       GinkgoHelper()
        c.suite.log(containerExecCommand)
        c.suite.assertNil(exechelper.Run(containerExecCommand))
 }
@@ -282,7 +283,7 @@ func (c *Container) exec(command string, arguments ...any) string {
        cliCommand := fmt.Sprintf(command, arguments...)
        containerExecCommand := "docker exec" + c.getEnvVarsAsCliOption() +
                " " + c.name + " " + cliCommand
-       c.suite.T().Helper()
+       GinkgoHelper()
        c.suite.log(containerExecCommand)
        byteOutput, err := exechelper.CombinedOutput(containerExecCommand)
        c.suite.assertNil(err, err)
@@ -291,12 +292,12 @@ func (c *Container) exec(command string, arguments ...any) string {
 
 func (c *Container) getLogDirPath() string {
        testId := c.suite.getTestId()
-       testName := c.suite.T().Name()
+       testName := CurrentSpecReport().LeafNodeText
        logDirPath := logDir + testName + "/" + testId + "/"
 
        cmd := exec.Command("mkdir", "-p", logDirPath)
        if err := cmd.Run(); err != nil {
-               c.suite.T().Fatalf("mkdir error: %v", err)
+               Fail("mkdir error: " + fmt.Sprint(err))
        }
 
        return logDirPath
@@ -313,12 +314,12 @@ func (c *Container) saveLogs() {
        cmd = exec.Command("docker", "logs", "--details", "-t", c.name)
        output, err := cmd.CombinedOutput()
        if err != nil {
-               c.suite.T().Fatalf("fetching logs error: %v", err)
+               Fail("fetching logs error: " + fmt.Sprint(err))
        }
 
        f, err := os.Create(testLogFilePath)
        if err != nil {
-               c.suite.T().Fatalf("file create error: %v", err)
+               Fail("file create error: " + fmt.Sprint(err))
        }
        fmt.Fprint(f, string(output))
        f.Close()
index e17bc11..9a034ed 100644 (file)
@@ -36,7 +36,7 @@ func (c *CpuAllocatorT) Allocate(nCpus int) (*CpuContext, error) {
        return &cpuCtx, nil
 }
 
-func (c *CpuAllocatorT) readCpus(fname string) error {
+func (c *CpuAllocatorT) readCpus() error {
        var first, last int
        file, err := os.Open(CPU_PATH)
        if err != nil {
@@ -60,7 +60,7 @@ func (c *CpuAllocatorT) readCpus(fname string) error {
 func CpuAllocator() (*CpuAllocatorT, error) {
        if cpuAllocator == nil {
                cpuAllocator = new(CpuAllocatorT)
-               err := cpuAllocator.readCpus(CPU_PATH)
+               err := cpuAllocator.readCpus()
                if err != nil {
                        return nil, err
                }
index 6b05758..a8c9847 100644 (file)
@@ -16,6 +16,7 @@ COPY \
    $DIR/unittest_plugin.so \
    $DIR/quic_plugin.so \
    $DIR/http_static_plugin.so \
+   $DIR/ping_plugin.so \
    $DIR/prom_plugin.so \
    $DIR/tlsopenssl_plugin.so \
    /usr/lib/x86_64-linux-gnu/vpp_plugins/
index 690f6d1..710163c 100644 (file)
@@ -1,6 +1,11 @@
 package main
 
-func (s *VethsSuite) TestEchoBuiltin() {
+func init() {
+       registerVethTests(EchoBuiltinTest)
+       registerSoloVethTests(TcpWithLossTest)
+}
+
+func EchoBuiltinTest(s *VethsSuite) {
        serverVpp := s.getContainerByName("server-vpp").vppInstance
        serverVeth := s.getInterfaceByName(serverInterfaceName)
 
@@ -16,7 +21,7 @@ func (s *VethsSuite) TestEchoBuiltin() {
        s.assertNotContains(o, "failed:")
 }
 
-func (s *VethsSuite) TestTcpWithLoss() {
+func TcpWithLossTest(s *VethsSuite) {
        serverVpp := s.getContainerByName("server-vpp").vppInstance
 
        serverVeth := s.getInterfaceByName(serverInterfaceName)
@@ -25,9 +30,12 @@ func (s *VethsSuite) TestTcpWithLoss() {
 
        clientVpp := s.getContainerByName("client-vpp").vppInstance
 
+       // TODO: investigate why this ping was here:
+       // ---------
        // Ensure that VPP doesn't abort itself with NSIM enabled
        // Warning: Removing this ping will make the test fail!
-       clientVpp.vppctl("ping %s", serverVeth.ip4AddressString())
+       // clientVpp.vppctl("ping %s", serverVeth.ip4AddressString())
+       // ---------
 
        // Add loss of packets with Network Delay Simulator
        clientVpp.vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" +
index 84aa570..8773fa2 100644 (file)
@@ -3,30 +3,11 @@ package main
 import (
        "testing"
 
-       "github.com/stretchr/testify/suite"
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
 )
 
-func TestTapSuite(t *testing.T) {
-       var m TapSuite
-       suite.Run(t, &m)
-}
-
-func TestNs(t *testing.T) {
-       var m NsSuite
-       suite.Run(t, &m)
-}
-
-func TestVeths(t *testing.T) {
-       var m VethsSuite
-       suite.Run(t, &m)
-}
-
-func TestNoTopo(t *testing.T) {
-       var m NoTopoSuite
-       suite.Run(t, &m)
-}
-
-func TestNginx(t *testing.T) {
-       var m NginxSuite
-       suite.Run(t, &m)
+func TestHst(t *testing.T) {
+       RegisterFailHandler(Fail)
+       RunSpecs(t, "HST")
 }
index 00e1213..50d83a4 100644 (file)
@@ -4,19 +4,28 @@ go 1.21
 
 require (
        github.com/edwarnicke/exechelper v1.0.3
-       github.com/stretchr/testify v1.8.4
        go.fd.io/govpp v0.9.0
        gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
-       github.com/davecgh/go-spew v1.1.1 // indirect
+       github.com/go-logr/logr v1.4.1 // indirect
+       github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
+       github.com/google/go-cmp v0.6.0 // indirect
+       github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
+       golang.org/x/net v0.20.0 // indirect
+       golang.org/x/text v0.14.0 // indirect
+       golang.org/x/tools v0.17.0 // indirect
+)
+
+require (
        github.com/fsnotify/fsnotify v1.7.0 // indirect
        github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
        github.com/kr/text v0.2.0 // indirect
        github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
+       github.com/onsi/ginkgo/v2 v2.16.0
+       github.com/onsi/gomega v1.30.0
        github.com/pkg/errors v0.9.1 // indirect
-       github.com/pmezard/go-difflib v1.0.0 // indirect
        github.com/sirupsen/logrus v1.9.3 // indirect
        github.com/vishvananda/netns v0.0.4 // indirect
        golang.org/x/sys v0.16.0 // indirect
index df59673..0070725 100644 (file)
@@ -1,3 +1,6 @@
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -6,10 +9,19 @@ github.com/edwarnicke/exechelper v1.0.3 h1:OY2ocGAITTqnEDvZk0dRQSeMIQvyH0SyL/4nc
 github.com/edwarnicke/exechelper v1.0.3/go.mod h1:R65OUPKns4bgeHkCmfSHbmqLBU8aHZxTgLmEyUBUk4U=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
+github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -18,6 +30,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
+github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
+github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
 github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
 github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -28,22 +42,27 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
 github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
 github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
 go.fd.io/govpp v0.9.0 h1:EHUXhQ+dph2K2An4YMqmd/WBE3Fcqsg97KVmdLJoSoU=
 go.fd.io/govpp v0.9.0/go.mod h1:9QoqjEbvfuuXNfjHS0A7YS+7QQVVaQ9cMioOWpSM4rY=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
 golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
+golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
index c5c8edb..4c6d5b2 100644 (file)
@@ -5,14 +5,15 @@ import (
        "errors"
        "flag"
        "fmt"
+       "log/slog"
        "os"
        "os/exec"
        "strings"
        "time"
 
        "github.com/edwarnicke/exechelper"
-       "github.com/stretchr/testify/assert"
-       "github.com/stretchr/testify/suite"
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
        "gopkg.in/yaml.v3"
 )
 
@@ -28,7 +29,6 @@ var nConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
 var vppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
 
 type HstSuite struct {
-       suite.Suite
        containers       map[string]*Container
        volumes          []string
        netConfigs       []NetConfig
@@ -38,7 +38,7 @@ type HstSuite struct {
        cpuAllocator     *CpuAllocatorT
        cpuContexts      []*CpuContext
        cpuPerVpp        int
-       pid                              string
+       pid              string
 }
 
 func (s *HstSuite) SetupSuite() {
@@ -46,7 +46,7 @@ func (s *HstSuite) SetupSuite() {
        s.pid = fmt.Sprint(os.Getpid())
        s.cpuAllocator, err = CpuAllocator()
        if err != nil {
-               s.FailNow("failed to init cpu allocator: %v", err)
+               Fail("failed to init cpu allocator: " + fmt.Sprint(err))
        }
        s.cpuPerVpp = *nConfiguredCpus
 }
@@ -85,6 +85,10 @@ func (s *HstSuite) skipIfUnconfiguring() {
 }
 
 func (s *HstSuite) SetupTest() {
+       RegisterFailHandler(func(message string, callerSkip ...int) {
+               s.hstFail()
+               Fail(message, callerSkip...)
+       })
        s.skipIfUnconfiguring()
        s.setupVolumes()
        s.setupContainers()
@@ -106,15 +110,15 @@ func (s *HstSuite) setupContainers() {
        }
 }
 
-func logVppInstance(container *Container, maxLines int){
-       if container.vppInstance == nil{
+func logVppInstance(container *Container, maxLines int) {
+       if container.vppInstance == nil {
                return
        }
 
        logSource := container.getHostWorkDir() + defaultLogFilePath
        file, err := os.Open(logSource)
 
-       if err != nil{
+       if err != nil {
                return
        }
        defer file.Close()
@@ -123,7 +127,7 @@ func logVppInstance(container *Container, maxLines int){
        var lines []string
        var counter int
 
-       for scanner.Scan(){
+       for scanner.Scan() {
                lines = append(lines, scanner.Text())
                counter++
                if counter > maxLines {
@@ -133,7 +137,7 @@ func logVppInstance(container *Container, maxLines int){
        }
 
        fmt.Println("vvvvvvvvvvvvvvv " + container.name + " [VPP instance]:")
-       for _, line := range lines{
+       for _, line := range lines {
                fmt.Println(line)
        }
        fmt.Printf("^^^^^^^^^^^^^^^\n\n")
@@ -141,73 +145,56 @@ func logVppInstance(container *Container, maxLines int){
 
 func (s *HstSuite) hstFail() {
        fmt.Println("Containers: " + fmt.Sprint(s.containers))
-       for _, container := range s.containers{
+       for _, container := range s.containers {
                out, err := container.log(20)
-               if err != nil{
+               if err != nil {
                        fmt.Printf("An error occured while obtaining '%s' container logs: %s\n", container.name, fmt.Sprint(err))
                        break
                }
                fmt.Printf("\nvvvvvvvvvvvvvvv " +
-                                       container.name + ":\n" +
-                                       out +
-                                       "^^^^^^^^^^^^^^^\n\n")
+                       container.name + ":\n" +
+                       out +
+                       "^^^^^^^^^^^^^^^\n\n")
                logVppInstance(container, 20)
        }
-       s.T().FailNow()
 }
 
 func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
-       if !assert.Nil(s.T(), object, msgAndArgs...) {
-               s.hstFail()
-       }
+       Expect(object).To(BeNil(), msgAndArgs...)
 }
 
 func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
-       if !assert.NotNil(s.T(), object, msgAndArgs...) {
-               s.hstFail()
-       }
+       Expect(object).ToNot(BeNil(), msgAndArgs...)
 }
 
 func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
-       if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
-               s.hstFail()
-       }
+       Expect(actual).To(Equal(expected), msgAndArgs...)
 }
 
 func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
-       if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
-               s.hstFail()
-       }
+       Expect(actual).ToNot(Equal(expected), msgAndArgs...)
 }
 
 func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
-       if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
-               s.hstFail()
-       }
+       Expect(testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
 }
 
 func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
-       if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
-               s.hstFail()
-       }
+       Expect(testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
 }
 
 func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
-       if !assert.NotEmpty(s.T(), object, msgAndArgs...) {
-               s.hstFail()
-       }
+       Expect(object).ToNot(BeEmpty(), msgAndArgs...)
 }
 
-func (s *HstSuite) log(args ...any) {
+func (s *HstSuite) log(arg any) {
        if *isVerbose {
-               s.T().Helper()
-               s.T().Log(args...)
+               slog.Info(fmt.Sprint(arg))
        }
 }
 
-func (s *HstSuite) skip(args ...any) {
-       s.log(args...)
-       s.T().SkipNow()
+func (s *HstSuite) skip(args string) {
+       Skip(args)
 }
 
 func (s *HstSuite) SkipIfMultiWorker(args ...any) {
@@ -249,11 +236,11 @@ func (s *HstSuite) getNetNamespaceByName(name string) string {
 }
 
 func (s *HstSuite) getInterfaceByName(name string) *NetInterface {
-       return s.netInterfaces[name + s.pid]
+       return s.netInterfaces[name+s.pid]
 }
 
 func (s *HstSuite) getContainerByName(name string) *Container {
-       return s.containers[name + s.pid]
+       return s.containers[name+s.pid]
 }
 
 /*
@@ -261,25 +248,25 @@ func (s *HstSuite) getContainerByName(name string) *Container {
  * are not able to modify the original container and affect other tests by doing that
  */
 func (s *HstSuite) getTransientContainerByName(name string) *Container {
-       containerCopy := *s.containers[name + s.pid]
+       containerCopy := *s.containers[name+s.pid]
        return &containerCopy
 }
 
 func (s *HstSuite) loadContainerTopology(topologyName string) {
        data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml")
        if err != nil {
-               s.T().Fatalf("read error: %v", err)
+               Fail("read error: " + fmt.Sprint(err))
        }
        var yamlTopo YamlTopology
        err = yaml.Unmarshal(data, &yamlTopo)
        if err != nil {
-               s.T().Fatalf("unmarshal error: %v", err)
+               Fail("unmarshal error: " + fmt.Sprint(err))
        }
 
        for _, elem := range yamlTopo.Volumes {
                volumeMap := elem["volume"].(VolumeConfig)
                hostDir := volumeMap["host-dir"].(string)
-               workingVolumeDir := logDir + s.T().Name() + s.pid + volumeDir
+               workingVolumeDir := logDir + CurrentSpecReport().LeafNodeText + s.pid + volumeDir
                volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
                hostDir = volDirReplacer.Replace(hostDir)
                s.volumes = append(s.volumes, hostDir)
@@ -287,10 +274,11 @@ func (s *HstSuite) loadContainerTopology(topologyName string) {
 
        s.containers = make(map[string]*Container)
        for _, elem := range yamlTopo.Containers {
-               newContainer, err := newContainer(s, elem, s.pid)
+               newContainer, err := newContainer(s, elem)
                newContainer.suite = s
+               newContainer.name += newContainer.suite.pid
                if err != nil {
-                       s.T().Fatalf("container config error: %v", err)
+                       Fail("container config error: " + fmt.Sprint(err))
                }
                s.containers[newContainer.name] = newContainer
        }
@@ -299,12 +287,12 @@ func (s *HstSuite) loadContainerTopology(topologyName string) {
 func (s *HstSuite) loadNetworkTopology(topologyName string) {
        data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml")
        if err != nil {
-               s.T().Fatalf("read error: %v", err)
+               Fail("read error: " + fmt.Sprint(err))
        }
        var yamlTopo YamlTopology
        err = yaml.Unmarshal(data, &yamlTopo)
        if err != nil {
-               s.T().Fatalf("unmarshal error: %v", err)
+               Fail("unmarshal error: " + fmt.Sprint(err))
        }
 
        s.ip4AddrAllocator = NewIp4AddressAllocator()
@@ -316,10 +304,10 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) {
                }
 
                if peer, ok := elem["peer"].(NetDevConfig); ok {
-                       if peer["name"].(string) != ""{
+                       if peer["name"].(string) != "" {
                                peer["name"] = peer["name"].(string) + s.pid
                        }
-                       if _, ok := peer["netns"]; ok{
+                       if _, ok := peer["netns"]; ok {
                                peer["netns"] = peer["netns"].(string) + s.pid
                        }
                }
@@ -341,7 +329,7 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) {
                                if namespace, err := newNetNamespace(elem); err == nil {
                                        s.netConfigs = append(s.netConfigs, &namespace)
                                } else {
-                                       s.T().Fatalf("network config error: %v", err)
+                                       Fail("network config error: " + fmt.Sprint(err))
                                }
                        }
                case Veth, Tap:
@@ -350,7 +338,7 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) {
                                        s.netConfigs = append(s.netConfigs, netIf)
                                        s.netInterfaces[netIf.Name()] = netIf
                                } else {
-                                       s.T().Fatalf("network config error: %v", err)
+                                       Fail("network config error: " + fmt.Sprint(err))
                                }
                        }
                case Bridge:
@@ -358,7 +346,7 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) {
                                if bridge, err := newBridge(elem); err == nil {
                                        s.netConfigs = append(s.netConfigs, &bridge)
                                } else {
-                                       s.T().Fatalf("network config error: %v", err)
+                                       Fail("network config error: " + fmt.Sprint(err))
                                }
                        }
                }
@@ -374,7 +362,7 @@ func (s *HstSuite) configureNetworkTopology(topologyName string) {
 
        for _, nc := range s.netConfigs {
                if err := nc.configure(); err != nil {
-                       s.T().Fatalf("network config error: %v", err)
+                       Fail("Network config error: " + fmt.Sprint(err))
                }
        }
 }
@@ -389,7 +377,7 @@ func (s *HstSuite) unconfigureNetworkTopology() {
 }
 
 func (s *HstSuite) getTestId() string {
-       testName := s.T().Name()
+       testName := CurrentSpecReport().LeafNodeText
 
        if s.testIds == nil {
                s.testIds = map[string]string{}
index 943c8a5..4277d43 100644 (file)
@@ -4,9 +4,20 @@ import (
        "fmt"
        "os"
        "strings"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
 )
 
-func (s *NsSuite) TestHttpTps() {
+func init() {
+       registerNsTests(HttpTpsTest)
+       registerVethTests(HttpCliTest)
+       registerNoTopoTests(NginxHttp3Test, NginxAsServerTest,
+               NginxPerfCpsTest, NginxPerfRpsTest, NginxPerfWrkTest)
+       registerNoTopoSoloTests(HttpStaticPromTest)
+}
+
+func HttpTpsTest(s *NsSuite) {
        iface := s.getInterfaceByName(clientInterface)
        client_ip := iface.ip4AddressString()
        port := "8080"
@@ -18,13 +29,16 @@ func (s *NsSuite) TestHttpTps() {
        // configure vpp in the container
        container.vppInstance.vppctl("http tps uri tcp://0.0.0.0/8080")
 
-       go s.startWget(finished, client_ip, port, "test_file_10M", clientNetns)
+       go func() {
+               defer GinkgoRecover()
+               s.startWget(finished, client_ip, port, "test_file_10M", clientNetns)
+       }()
        // wait for client
        err := <-finished
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
 }
 
-func (s *VethsSuite) TestHttpCli() {
+func HttpCliTest(s *VethsSuite) {
        serverContainer := s.getContainerByName("server-vpp")
        clientContainer := s.getContainerByName("client-vpp")
 
@@ -41,7 +55,7 @@ func (s *VethsSuite) TestHttpCli() {
        s.assertContains(o, "<html>", "<html> not found in the result!")
 }
 
-func (s *NoTopoSuite) TestNginxHttp3() {
+func NginxHttp3Test(s *NoTopoSuite) {
        s.SkipUnlessExtendedTestsBuilt()
 
        query := "index.html"
@@ -57,23 +71,27 @@ func (s *NoTopoSuite) TestNginxHttp3() {
        args := fmt.Sprintf("curl --noproxy '*' --local-port 55444 --http3-only -k https://%s:8443/%s", serverAddress, query)
        curlCont.extraRunningArgs = args
        o, err := curlCont.combinedOutput()
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
        s.assertContains(o, "<http>", "<http> not found in the result!")
 }
 
-func (s *NoTopoSuite) TestHttpStaticProm() {
+func HttpStaticPromTest(s *NoTopoSuite) {
        finished := make(chan error, 1)
        query := "stats.prom"
        vpp := s.getContainerByName("vpp").vppInstance
        serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
        s.log(vpp.vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
        s.log(vpp.vppctl("prom enable"))
-       go s.startWget(finished, serverAddress, "80", query, "")
+       time.Sleep(time.Second * 5)
+       go func() {
+               defer GinkgoRecover()
+               s.startWget(finished, serverAddress, "80", query, "")
+       }()
        err := <-finished
-       s.assertNil(err, err)
+       s.assertNil(err)
 }
 
-func (s *NoTopoSuite) TestNginxAsServer() {
+func NginxAsServerTest(s *NoTopoSuite) {
        query := "return_ok"
        finished := make(chan error, 1)
 
@@ -86,7 +104,10 @@ func (s *NoTopoSuite) TestNginxAsServer() {
        serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
 
        defer func() { os.Remove(query) }()
-       go s.startWget(finished, serverAddress, "80", query, "")
+       go func() {
+               defer GinkgoRecover()
+               s.startWget(finished, serverAddress, "80", query, "")
+       }()
        s.assertNil(<-finished)
 }
 
@@ -124,9 +145,11 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string) error {
                args += " -r"
                args += " http://" + serverAddress + ":80/64B.json"
                abCont.extraRunningArgs = args
+               time.Sleep(time.Second * 10)
                o, err := abCont.combinedOutput()
                rps := parseString(o, "Requests per second:")
-               s.log(rps, err)
+               s.log(rps)
+               s.log(err)
                s.assertNil(err, "err: '%s', output: '%s'", err, o)
        } else {
                wrkCont := s.getContainerByName("wrk")
@@ -135,20 +158,21 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string) error {
                wrkCont.extraRunningArgs = args
                o, err := wrkCont.combinedOutput()
                rps := parseString(o, "requests")
-               s.log(rps, err)
+               s.log(rps)
+               s.log(err)
                s.assertNil(err, "err: '%s', output: '%s'", err, o)
        }
        return nil
 }
 
-func (s *NoTopoSuite) TestNginxPerfCps() {
+func NginxPerfCpsTest(s *NoTopoSuite) {
        s.assertNil(runNginxPerf(s, "cps", "ab"))
 }
 
-func (s *NoTopoSuite) TestNginxPerfRps() {
+func NginxPerfRpsTest(s *NoTopoSuite) {
        s.assertNil(runNginxPerf(s, "rps", "ab"))
 }
 
-func (s *NoTopoSuite) TestNginxPerfWrk() {
+func NginxPerfWrkTest(s *NoTopoSuite) {
        s.assertNil(runNginxPerf(s, "", "wrk"))
 }
index 8d9168d..24d2de3 100644 (file)
@@ -3,9 +3,15 @@ package main
 import (
        "fmt"
        "os"
+
+       . "github.com/onsi/ginkgo/v2"
 )
 
-func (s *VethsSuite) TestLDPreloadIperfVpp() {
+func init() {
+       registerVethTests(LDPreloadIperfVppTest)
+}
+
+func LDPreloadIperfVppTest(s *VethsSuite) {
        var clnVclConf, srvVclConf Stanza
 
        serverContainer := s.getContainerByName("server-vpp")
@@ -14,10 +20,7 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() {
        clientContainer := s.getContainerByName("client-vpp")
        clientVclFileName := clientContainer.getHostWorkDir() + "/vcl_cln.conf"
 
-       ldpreload := os.Getenv("HST_LDPRELOAD")
-       s.assertNotEqual("", ldpreload)
-
-       ldpreload = "LD_PRELOAD=" + ldpreload
+       ldpreload := "LD_PRELOAD=../../build-root/build-vpp-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so"
 
        stopServerCh := make(chan struct{}, 1)
        srvCh := make(chan error, 1)
@@ -36,7 +39,7 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() {
                append("use-mq-eventfd").
                append(clientAppSocketApi).close().
                saveToFile(clientVclFileName)
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
 
        serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
                serverContainer.getHostWorkDir())
@@ -49,26 +52,32 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() {
                append("use-mq-eventfd").
                append(serverAppSocketApi).close().
                saveToFile(serverVclFileName)
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
 
        s.log("attaching server to vpp")
 
        srvEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+serverVclFileName)
-       go s.startServerApp(srvCh, stopServerCh, srvEnv)
+       go func() {
+               defer GinkgoRecover()
+               s.startServerApp(srvCh, stopServerCh, srvEnv)
+       }()
 
        err = <-srvCh
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
 
        s.log("attaching client to vpp")
        var clnRes = make(chan string, 1)
        clnEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+clientVclFileName)
        serverVethAddress := s.getInterfaceByName(serverInterfaceName).ip4AddressString()
-       go s.startClientApp(serverVethAddress, clnEnv, clnCh, clnRes)
+       go func() {
+               defer GinkgoRecover()
+               s.startClientApp(serverVethAddress, clnEnv, clnCh, clnRes)
+       }()
        s.log(<-clnRes)
 
        // wait for client's result
        err = <-clnCh
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
 
        // stop server
        stopServerCh <- struct{}{}
index 06247e4..e323f7f 100644 (file)
@@ -1,6 +1,16 @@
 package main
 
-func (s *TapSuite) TestLinuxIperf() {
+import (
+       "fmt"
+
+       . "github.com/onsi/ginkgo/v2"
+)
+
+func init() {
+       registerTapTests(LinuxIperfTest)
+}
+
+func LinuxIperfTest(s *TapSuite) {
        clnCh := make(chan error)
        stopServerCh := make(chan struct{})
        srvCh := make(chan error, 1)
@@ -9,13 +19,19 @@ func (s *TapSuite) TestLinuxIperf() {
                stopServerCh <- struct{}{}
        }()
 
-       go s.startServerApp(srvCh, stopServerCh, nil)
+       go func() {
+               defer GinkgoRecover()
+               s.startServerApp(srvCh, stopServerCh, nil)
+       }()
        err := <-srvCh
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
        s.log("server running")
 
        ipAddress := s.getInterfaceByName(tapInterfaceName).ip4AddressString()
-       go s.startClientApp(ipAddress, nil, clnCh, clnRes)
+       go func() {
+               defer GinkgoRecover()
+               s.startClientApp(ipAddress, nil, clnCh, clnRes)
+       }()
        s.log("client running")
        s.log(<-clnRes)
        err = <-clnCh
index 91f43f4..6c5a860 100644 (file)
@@ -4,7 +4,11 @@ import (
        "github.com/edwarnicke/exechelper"
 )
 
-func (s *NginxSuite) TestMirroring() {
+func init() {
+       registerNginxTests(MirroringTest)
+}
+
+func MirroringTest(s *NginxSuite) {
        proxyAddress := s.getInterfaceByName(mirroringClientInterfaceName).peer.ip4AddressString()
 
        path := "/64B.json"
index c2f9b6f..ac5f94c 100644 (file)
@@ -5,8 +5,13 @@ import (
        "os"
 
        "github.com/edwarnicke/exechelper"
+       . "github.com/onsi/ginkgo/v2"
 )
 
+func init() {
+       registerNsTests(VppProxyHttpTcpTest, VppProxyHttpTlsTest, EnvoyProxyHttpTcpTest)
+}
+
 func testProxyHttpTcp(s *NsSuite, proto string) error {
        var outputFile string = "test" + s.pid + ".data"
        var srcFilePid string = "httpTestFile" + s.pid
@@ -19,12 +24,15 @@ func testProxyHttpTcp(s *NsSuite, proto string) error {
 
        // create test file
        err := exechelper.Run(fmt.Sprintf("ip netns exec %s truncate -s %s %s", serverNetns, fileSize, srcFilePid))
-       s.assertNil(err, "failed to run truncate command: " + fmt.Sprint(err))
+       s.assertNil(err, "failed to run truncate command: "+fmt.Sprint(err))
        defer func() { os.Remove(srcFilePid) }()
 
        s.log("test file created...")
 
-       go s.startHttpServer(serverRunning, stopServer, ":666", serverNetns)
+       go func() {
+               defer GinkgoRecover()
+               s.startHttpServer(serverRunning, stopServer, ":666", serverNetns)
+       }()
        // TODO better error handling and recovery
        <-serverRunning
 
@@ -64,21 +72,21 @@ func configureVppProxy(s *NsSuite, proto string) {
                clientVeth.ip4AddressString(),
                serverVeth.peer.ip4AddressString(),
        )
-       s.log("proxy configured...", output)
+       s.log("proxy configured: " + output)
 }
 
-func (s *NsSuite) TestVppProxyHttpTcp() {
+func VppProxyHttpTcpTest(s *NsSuite) {
        proto := "tcp"
        configureVppProxy(s, proto)
        err := testProxyHttpTcp(s, proto)
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
 }
 
-func (s *NsSuite) TestVppProxyHttpTls() {
+func VppProxyHttpTlsTest(s *NsSuite) {
        proto := "tls"
        configureVppProxy(s, proto)
        err := testProxyHttpTcp(s, proto)
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
 }
 
 func configureEnvoyProxy(s *NsSuite) {
@@ -100,8 +108,8 @@ func configureEnvoyProxy(s *NsSuite) {
        s.assertNil(envoyContainer.start())
 }
 
-func (s *NsSuite) TestEnvoyProxyHttpTcp() {
+func EnvoyProxyHttpTcpTest(s *NsSuite) {
        configureEnvoyProxy(s)
        err := testProxyHttpTcp(s, "tcp")
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
 }
index 670ed58..cf74c62 100644 (file)
@@ -1,15 +1,20 @@
 package main
 
-func (s *VethsSuite) TestVppEchoQuic() {
+func init() {
+       registerVethTests(VppEchoQuicTest, VppEchoTcpTest, VppEchoUdpTest)
+}
+
+func VppEchoQuicTest(s *VethsSuite) {
        s.testVppEcho("quic")
 }
 
 // udp echo currently broken in vpp, skipping
-func (s *VethsSuite) SkipTestVppEchoUdp() {
+func VppEchoUdpTest(s *VethsSuite) {
+       s.skip("Broken")
        s.testVppEcho("udp")
 }
 
-func (s *VethsSuite) TestVppEchoTcp() {
+func VppEchoTcpTest(s *VethsSuite) {
        s.testVppEcho("tcp")
 }
 
index 8f40590..2d1caf1 100644 (file)
@@ -1,18 +1,37 @@
 package main
 
+import (
+       "reflect"
+       "runtime"
+       "strings"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+)
+
 // These correspond to names used in yaml config
 const (
-       vppProxyContainerName            = "vpp-proxy"
-       nginxProxyContainerName          = "nginx-proxy"
-       nginxServerContainerName         = "nginx-server"
+       vppProxyContainerName        = "vpp-proxy"
+       nginxProxyContainerName      = "nginx-proxy"
+       nginxServerContainerName     = "nginx-server"
        mirroringClientInterfaceName = "hstcln"
        mirroringServerInterfaceName = "hstsrv"
 )
 
+var nginxTests = []func(s *NginxSuite){}
+var nginxSoloTests = []func(s *NginxSuite){}
+
 type NginxSuite struct {
        HstSuite
 }
 
+func registerNginxTests(tests ...func(s *NginxSuite)) {
+       nginxTests = append(nginxTests, tests...)
+}
+func registerNginxSoloTests(tests ...func(s *NginxSuite)) {
+       nginxSoloTests = append(nginxSoloTests, tests...)
+}
+
 func (s *NginxSuite) SetupSuite() {
        s.HstSuite.SetupSuite()
        s.loadNetworkTopology("2taps")
@@ -60,3 +79,51 @@ func (s *NginxSuite) SetupTest() {
 
        proxyVpp.waitForApp("nginx-", 5)
 }
+
+var _ = Describe("NginxSuite", Ordered, ContinueOnFailure, func() {
+       var s NginxSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+       for _, test := range nginxTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
+
+var _ = Describe("NginxSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+       var s NginxSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+       for _, test := range nginxSoloTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
index bbf0cfd..6df06c7 100644 (file)
@@ -1,15 +1,34 @@
 package main
 
+import (
+       "reflect"
+       "runtime"
+       "strings"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+)
+
 const (
-       singleTopoContainerVpp          = "vpp"
-       singleTopoContainerNginx        = "nginx"
-       tapInterfaceName                        = "htaphost"
+       singleTopoContainerVpp   = "vpp"
+       singleTopoContainerNginx = "nginx"
+       tapInterfaceName         = "htaphost"
 )
 
+var noTopoTests = []func(s *NoTopoSuite){}
+var noTopoSoloTests = []func(s *NoTopoSuite){}
+
 type NoTopoSuite struct {
        HstSuite
 }
 
+func registerNoTopoTests(tests ...func(s *NoTopoSuite)) {
+       noTopoTests = append(noTopoTests, tests...)
+}
+func registerNoTopoSoloTests(tests ...func(s *NoTopoSuite)) {
+       noTopoSoloTests = append(noTopoSoloTests, tests...)
+}
+
 func (s *NoTopoSuite) SetupSuite() {
        s.HstSuite.SetupSuite()
        s.loadNetworkTopology("tap")
@@ -35,3 +54,53 @@ func (s *NoTopoSuite) SetupTest() {
 
        s.assertNil(vpp.createTap(tapInterface), "failed to create tap interface")
 }
+
+var _ = Describe("NoTopoSuite", Ordered, ContinueOnFailure, func() {
+       var s NoTopoSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+
+       for _, test := range noTopoTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
+
+var _ = Describe("NoTopoSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+       var s NoTopoSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+
+       for _, test := range noTopoSoloTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
index 46d5bef..86d9b78 100644 (file)
@@ -1,15 +1,35 @@
 package main
 
+import (
+       "fmt"
+       "reflect"
+       "runtime"
+       "strings"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+)
+
 // These correspond to names used in yaml config
 const (
        clientInterface = "hclnvpp"
        serverInterface = "hsrvvpp"
 )
 
+var nsTests = []func(s *NsSuite){}
+var nsSoloTests = []func(s *NsSuite){}
+
 type NsSuite struct {
        HstSuite
 }
 
+func registerNsTests(tests ...func(s *NsSuite)) {
+       nsTests = append(nsTests, tests...)
+}
+func registerNsSoloTests(tests ...func(s *NsSuite)) {
+       nsSoloTests = append(nsSoloTests, tests...)
+}
+
 func (s *NsSuite) SetupSuite() {
        s.HstSuite.SetupSuite()
        s.configureNetworkTopology("ns")
@@ -34,12 +54,62 @@ func (s *NsSuite) SetupTest() {
        s.assertNil(vpp.start())
 
        idx, err := vpp.createAfPacket(s.getInterfaceByName(serverInterface))
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
        s.assertNotEqual(0, idx)
 
        idx, err = vpp.createAfPacket(s.getInterfaceByName(clientInterface))
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
        s.assertNotEqual(0, idx)
 
        container.exec("chmod 777 -R %s", container.getContainerWorkDir())
 }
+
+var _ = Describe("NsSuite", Ordered, ContinueOnFailure, func() {
+       var s NsSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+
+       for _, test := range nsTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
+
+var _ = Describe("NsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+       var s NsSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+
+       for _, test := range nsSoloTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
index 8b0950a..25c1e25 100644 (file)
@@ -1,15 +1,80 @@
 package main
 
 import (
+       "reflect"
+       "runtime"
+       "strings"
        "time"
+
+       . "github.com/onsi/ginkgo/v2"
 )
 
 type TapSuite struct {
        HstSuite
 }
 
+var tapTests = []func(s *TapSuite){}
+var tapSoloTests = []func(s *TapSuite){}
+
+func registerTapTests(tests ...func(s *TapSuite)) {
+       tapTests = append(tapTests, tests...)
+}
+func registerTapSoloTests(tests ...func(s *TapSuite)) {
+       tapSoloTests = append(tapSoloTests, tests...)
+}
+
 func (s *TapSuite) SetupSuite() {
        time.Sleep(1 * time.Second)
        s.HstSuite.SetupSuite()
        s.configureNetworkTopology("tap")
 }
+
+var _ = Describe("TapSuite", Ordered, ContinueOnFailure, func() {
+       var s TapSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+
+       for _, test := range tapTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
+
+var _ = Describe("TapSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+       var s TapSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+
+       for _, test := range tapSoloTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
index 061eee0..f4c3684 100644 (file)
@@ -1,7 +1,13 @@
 package main
 
 import (
+       "fmt"
+       "reflect"
+       "runtime"
+       "strings"
        "time"
+
+       . "github.com/onsi/ginkgo/v2"
 )
 
 // These correspond to names used in yaml config
@@ -10,10 +16,20 @@ const (
        clientInterfaceName = "cln"
 )
 
+var vethTests = []func(s *VethsSuite){}
+var vethSoloTests = []func(s *VethsSuite){}
+
 type VethsSuite struct {
        HstSuite
 }
 
+func registerVethTests(tests ...func(s *VethsSuite)) {
+       vethTests = append(vethTests, tests...)
+}
+func registerSoloVethTests(tests ...func(s *VethsSuite)) {
+       vethSoloTests = append(vethSoloTests, tests...)
+}
+
 func (s *VethsSuite) SetupSuite() {
        time.Sleep(1 * time.Second)
        s.HstSuite.SetupSuite()
@@ -36,7 +52,7 @@ func (s *VethsSuite) SetupTest() {
 
        cpus := s.AllocateCpus()
        serverVpp, err := serverContainer.newVppInstance(cpus, sessionConfig)
-       s.assertNotNil(serverVpp, err)
+       s.assertNotNil(serverVpp, fmt.Sprint(err))
 
        s.setupServerVpp()
 
@@ -45,7 +61,7 @@ func (s *VethsSuite) SetupTest() {
 
        cpus = s.AllocateCpus()
        clientVpp, err := clientContainer.newVppInstance(cpus, sessionConfig)
-       s.assertNotNil(clientVpp, err)
+       s.assertNotNil(clientVpp, fmt.Sprint(err))
 
        s.setupClientVpp()
 }
@@ -56,7 +72,7 @@ func (s *VethsSuite) setupServerVpp() {
 
        serverVeth := s.getInterfaceByName(serverInterfaceName)
        idx, err := serverVpp.createAfPacket(serverVeth)
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
        s.assertNotEqual(0, idx)
 }
 
@@ -66,6 +82,60 @@ func (s *VethsSuite) setupClientVpp() {
 
        clientVeth := s.getInterfaceByName(clientInterfaceName)
        idx, err := clientVpp.createAfPacket(clientVeth)
-       s.assertNil(err, err)
+       s.assertNil(err, fmt.Sprint(err))
        s.assertNotEqual(0, idx)
 }
+
+var _ = Describe("VethsSuite", Ordered, ContinueOnFailure, func() {
+       var s VethsSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+
+       // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+       for _, test := range vethTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
+
+var _ = Describe("VethsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+       var s VethsSuite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TearDownSuite()
+
+       })
+       AfterEach(func() {
+               s.TearDownTest()
+       })
+
+       // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+       for _, test := range vethSoloTests {
+               test := test
+               pc := reflect.ValueOf(test).Pointer()
+               funcValue := runtime.FuncForPC(pc)
+               It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) {
+                       test(&s)
+               }, SpecTimeout(time.Minute*5))
+       }
+})
index c3b9eae..9b18e1b 100755 (executable)
@@ -8,6 +8,8 @@ persist_set=0
 unconfigure_set=0
 debug_set=0
 vppsrc=
+ginkgo_args=
+parallel=
 
 for i in "$@"
 do
@@ -49,8 +51,14 @@ case "${i}" in
         tc_name="${i#*=}"
         if [ $tc_name != "all" ]; then
             single_test=1
-            args="$args -run $tc_name -verbose"
+            ginkgo_args="$ginkgo_args --focus $tc_name -vv"
+            args="$args -verbose"
+        else
+            ginkgo_args="$ginkgo_args -v"
         fi
+        ;;
+    --parallel=*)
+        ginkgo_args="$ginkgo_args -procs=${i#*=}"
 esac
 done
 
@@ -74,4 +82,4 @@ if [ $single_test -eq 0 ] && [ $debug_set -eq 1 ]; then
     exit 1
 fi
 
-sudo -E go test -timeout=20m -buildvcs=false -v $args
+sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --trace $ginkgo_args -- $args
index cb6aaa4..fdcd60a 100644 (file)
@@ -5,6 +5,11 @@ import (
        "time"
 )
 
+func init() {
+       registerVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest,
+               XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclRetryAttachTest)
+}
+
 func getVclConfig(c *Container, ns_id_optional ...string) string {
        var s Stanza
        ns_id := "default"
@@ -23,11 +28,11 @@ func getVclConfig(c *Container, ns_id_optional ...string) string {
        return s.close().toString()
 }
 
-func (s *VethsSuite) TestXEchoVclClientUdp() {
+func XEchoVclClientUdpTest(s *VethsSuite) {
        s.testXEchoVclClient("udp")
 }
 
-func (s *VethsSuite) TestXEchoVclClientTcp() {
+func XEchoVclClientTcpTest(s *VethsSuite) {
        s.testXEchoVclClient("tcp")
 }
 
@@ -49,11 +54,11 @@ func (s *VethsSuite) testXEchoVclClient(proto string) {
        s.assertContains(o, "CLIENT RESULTS")
 }
 
-func (s *VethsSuite) TestXEchoVclServerUdp() {
+func XEchoVclServerUdpTest(s *VethsSuite) {
        s.testXEchoVclServer("udp")
 }
 
-func (s *VethsSuite) TestXEchoVclServerTcp() {
+func XEchoVclServerTcpTest(s *VethsSuite) {
        s.testXEchoVclServer("tcp")
 }
 
@@ -97,16 +102,15 @@ func (s *VethsSuite) testVclEcho(proto string) {
        s.log(o)
 }
 
-func (s *VethsSuite) TestVclEchoTcp() {
+func VclEchoTcpTest(s *VethsSuite) {
        s.testVclEcho("tcp")
 }
 
-func (s *VethsSuite) TestVclEchoUdp() {
+func VclEchoUdpTest(s *VethsSuite) {
        s.testVclEcho("udp")
 }
 
-// this test takes too long, for now it's being skipped
-func (s *VethsSuite) SkipTestVclRetryAttach() {
+func VclRetryAttachTest(s *VethsSuite) {
        s.testRetryAttach("tcp")
 }
 
index 1a058c8..d78e6c5 100644 (file)
@@ -11,6 +11,7 @@ import (
        "time"
 
        "github.com/edwarnicke/exechelper"
+       . "github.com/onsi/ginkgo/v2"
 
        "go.fd.io/govpp"
        "go.fd.io/govpp/api"
@@ -59,6 +60,7 @@ plugins {
   plugin http_static_plugin.so { enable }
   plugin prom_plugin.so { enable }
   plugin tlsopenssl_plugin.so { enable }
+  plugin ping_plugin.so { enable }
 }
 
 logging {
@@ -131,9 +133,10 @@ func (vpp *VppInstance) start() error {
        vpp.container.createFile(vppcliFileName, cliContent)
        vpp.container.exec("chmod 0755 " + vppcliFileName)
 
+       vpp.getSuite().log("starting vpp")
        if *isVppDebug {
                sig := make(chan os.Signal, 1)
-               signal.Notify(sig, syscall.SIGINT)
+               signal.Notify(sig, syscall.SIGQUIT)
                cont := make(chan bool, 1)
                go func() {
                        <-sig
@@ -143,7 +146,7 @@ func (vpp *VppInstance) start() error {
                vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
                fmt.Println("run following command in different terminal:")
                fmt.Println("docker exec -it " + vpp.container.name + " gdb -ex \"attach $(docker exec " + vpp.container.name + " pidof vpp)\"")
-               fmt.Println("Afterwards press CTRL+C to continue")
+               fmt.Println("Afterwards press CTRL+\\ to continue")
                <-cont
                fmt.Println("continuing...")
        } else {
@@ -151,6 +154,7 @@ func (vpp *VppInstance) start() error {
                vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
        }
 
+       vpp.getSuite().log("connecting to vpp")
        // Connect to VPP and store the connection
        sockAddress := vpp.container.getHostWorkDir() + defaultApiSocketFilePath
        conn, connEv, err := govpp.AsyncConnect(
@@ -207,7 +211,7 @@ func (vpp *VppInstance) GetSessionStat(stat string) int {
                        tokens := strings.Split(strings.TrimSpace(line), " ")
                        val, err := strconv.Atoi(tokens[0])
                        if err != nil {
-                               vpp.getSuite().FailNow("failed to parse stat value %s", err)
+                               Fail("failed to parse stat value %s" + fmt.Sprint(err))
                                return 0
                        }
                        return val
@@ -217,6 +221,7 @@ func (vpp *VppInstance) GetSessionStat(stat string) int {
 }
 
 func (vpp *VppInstance) waitForApp(appName string, timeout int) {
+       vpp.getSuite().log("waiting for app " + appName)
        for i := 0; i < timeout; i++ {
                o := vpp.vppctl("show app")
                if strings.Contains(o, appName) {
@@ -240,6 +245,7 @@ func (vpp *VppInstance) createAfPacket(
        }
        createReply := &af_packet.AfPacketCreateV2Reply{}
 
+       vpp.getSuite().log("create af-packet interface " + veth.Name())
        if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
                return 0, err
        }
@@ -252,6 +258,7 @@ func (vpp *VppInstance) createAfPacket(
        }
        upReply := &interfaces.SwInterfaceSetFlagsReply{}
 
+       vpp.getSuite().log("set af-packet interface " + veth.Name() + " up")
        if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
                return 0, err
        }
@@ -273,6 +280,7 @@ func (vpp *VppInstance) createAfPacket(
        }
        addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
 
+       vpp.getSuite().log("af-packet interface " + veth.Name() + " add address " + veth.ip4Address)
        if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
                return 0, err
        }
@@ -292,6 +300,7 @@ func (vpp *VppInstance) addAppNamespace(
        }
        reply := &session.AppNamespaceAddDelV2Reply{}
 
+       vpp.getSuite().log("add app namespace " + namespaceId)
        if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
                return err
        }
@@ -301,6 +310,7 @@ func (vpp *VppInstance) addAppNamespace(
        }
        sessionReply := &session.SessionEnableDisableReply{}
 
+       vpp.getSuite().log("enable app namespace " + namespaceId)
        if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
                return err
        }
@@ -325,6 +335,7 @@ func (vpp *VppInstance) createTap(
        }
        createTapReply := &tapv2.TapCreateV2Reply{}
 
+       vpp.getSuite().log("create tap interface " + tap.Name())
        // Create tap interface
        if err := vpp.apiChannel.SendRequest(createTapReq).ReceiveReply(createTapReply); err != nil {
                return err
@@ -338,6 +349,7 @@ func (vpp *VppInstance) createTap(
        }
        addAddressReply := &interfaces.SwInterfaceAddDelAddressReply{}
 
+       vpp.getSuite().log("tap interface " + tap.Name() + " add address " + tap.peer.ip4Address)
        if err := vpp.apiChannel.SendRequest(addAddressReq).ReceiveReply(addAddressReply); err != nil {
                return err
        }
@@ -349,6 +361,7 @@ func (vpp *VppInstance) createTap(
        }
        upReply := &interfaces.SwInterfaceSetFlagsReply{}
 
+       vpp.getSuite().log("set tap interface " + tap.Name() + " up")
        if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
                return err
        }
@@ -360,7 +373,6 @@ func (vpp *VppInstance) saveLogs() {
        logTarget := vpp.container.getLogDirPath() + "vppinstance-" + vpp.container.name + ".log"
        logSource := vpp.container.getHostWorkDir() + defaultLogFilePath
        cmd := exec.Command("cp", logSource, logTarget)
-       vpp.getSuite().T().Helper()
        vpp.getSuite().log(cmd.String())
        cmd.Run()
 }