Browse Source

[master] Finishing merge of trac4501 (misc unit tests)

Francis Dupont 8 years ago
parent
commit
33235f0d97

+ 2 - 2
doc/examples/kea6/multiple-options.json

@@ -72,8 +72,8 @@
         },
         {
             // A few options are encoded in (length, string) tuples
-	    // which can be defined using only strings as the CSV
-	    // processing computes lengths.
+            // which can be defined using only strings as the CSV
+            // processing computes lengths.
             "name": "bootfile-param",
             "data": "root=/dev/sda2, quiet, splash"
         }

+ 52 - 52
doc/examples/kea6/reservations.json

@@ -44,11 +44,11 @@
       "pools": [ { "pool": "2001:db8:1::/120" } ],
 
       "pd-pools": [
-	  {
-	      "prefix": "2001:db8:1:8000::",
-	      "prefix-len": 56,
-	      "delegated-len": 64
-	  }
+          {
+              "prefix": "2001:db8:1:8000::",
+              "prefix-len": 56,
+              "delegated-len": 64
+          }
       ],
       "interface": "ethX",
 
@@ -62,10 +62,10 @@
       "reservations": [
 // This is a simple host reservation. The host with DUID matching
 // the specified value will get an address of 2001:db8:1::100.
-	  {
-	      "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
-	      "ip-addresses": [ "2001:db8:1::100" ]
-	  },
+          {
+              "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
+              "ip-addresses": [ "2001:db8:1::100" ]
+          },
 // This is similar to the previous one, but this time the reservation is done
 // based on hardware/MAC address. The server will do its best to extract
 // the hardware/MAC address from received packets (see 'mac-sources' directive
@@ -73,42 +73,42 @@
 // to be available for this client. If there are options with the same code
 // specified in a global, subnet or class scope, the values defined at host level
 // take precedence.
-	  {
-	      "hw-address": "00:01:02:03:04:05",
-	      "ip-addresses": [ "2001:db8:1::101" ],
-	      "option-data": [
-	      {
-		  "name": "dns-servers",
-		  "data": "3000:1::234"
-	      },
-	      {
-		  "name": "nis-servers",
-		  "data": "3000:1::234"
-	      }],
-	      "client-classes": [ "special_snowflake", "office" ]
-	  },
+          {
+              "hw-address": "00:01:02:03:04:05",
+              "ip-addresses": [ "2001:db8:1::101" ],
+              "option-data": [
+              {
+                  "name": "dns-servers",
+                  "data": "3000:1::234"
+              },
+              {
+                  "name": "nis-servers",
+                  "data": "3000:1::234"
+              }],
+              "client-classes": [ "special_snowflake", "office" ]
+          },
 // This is a bit more advanced reservation. The client with the specified
 // DUID will get a reserved address, a reserved prefix and a hostname.
 // This reservation is for an address that it not within the dynamic pool.
 // Finally, this reservation features vendor specific options for CableLabs,
 // which happen to use enterprise-id 4491. Those particular values will
 // be returned only to the client that has a DUID matching this reservation.
-	  {
-	      "duid": "01:02:03:04:05:06:07:08:09:0A",
-	      "ip-addresses": [ "2001:db8:1:cafe::1" ],
-	      "prefixes": [ "2001:db8:2:abcd::/64" ],
-	      "hostname": "foo.example.com",
-	      "option-data": [ {
-		  "name": "vendor-opts",
-		  "data": "4491"
-	      },
-	      {
-		  "name": "tftp-servers",
-		  "space": "vendor-4491",
-		  "data": "3000:1::234"
-	      } ]
+          {
+              "duid": "01:02:03:04:05:06:07:08:09:0A",
+              "ip-addresses": [ "2001:db8:1:cafe::1" ],
+              "prefixes": [ "2001:db8:2:abcd::/64" ],
+              "hostname": "foo.example.com",
+              "option-data": [ {
+                  "name": "vendor-opts",
+                  "data": "4491"
+              },
+              {
+                  "name": "tftp-servers",
+                  "space": "vendor-4491",
+                  "data": "3000:1::234"
+              } ]
 
-	  },
+          },
 // This reservation is using flexible identifier. Instead of relying on specific
 // field, sysadmin can define an expression similar to what is used for client
 // classification, e.g. substring(relay[0].option[17],0,6). Then, based on the
@@ -116,10 +116,10 @@
 // Expression can be specified either as hex or plain text using single
 // quotes.
 // Note: flexible identifier requires flex_id hook library to be loaded to work.
-	 {
-	     "flex-id": "'somevalue'",
-	     "ip-addresses": [ "2001:db8:1:cafe::2" ]
-	 }
+         {
+             "flex-id": "'somevalue'",
+             "ip-addresses": [ "2001:db8:1:cafe::2" ]
+         }
 
       ]
     }
@@ -130,16 +130,16 @@
 // informational level (info, warn, error and fatal) should be logged to stdout.
 "Logging": {
     "loggers": [
-	{
-	    "name": "kea-dhcp6",
-	    "output_options": [
-		{
-		    "output": "stdout"
-		}
-	    ],
-	    "debuglevel": 0,
-	    "severity": "INFO"
-	}
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "stdout"
+                }
+            ],
+            "debuglevel": 0,
+            "severity": "INFO"
+        }
     ]
 }
 

+ 3 - 1
src/bin/d2/d2_queue_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -33,6 +33,7 @@ public:
 
 /// @brief Thrown if the queue manager's receive handler is passed
 /// a failure result.
+/// @todo use or remove it.
 class D2QueueMgrReceiveError : public isc::Exception {
 public:
     D2QueueMgrReceiveError(const char* file, size_t line, const char* what) :
@@ -41,6 +42,7 @@ public:
 
 
 /// @brief Thrown if the request queue is full when an enqueue is attempted.
+/// @todo use or remove it.
 class D2QueueMgrQueueFull : public isc::Exception {
 public:
     D2QueueMgrQueueFull(const char* file, size_t line, const char* what) :

+ 2 - 0
src/bin/d2/tests/d2_cfg_mgr_unittests.cc

@@ -555,6 +555,7 @@ TEST_F(D2CfgMgrTest, fullConfig) {
     // Verify that the forward manager can be retrieved.
     DdnsDomainListMgrPtr mgr = context->getForwardMgr();
     ASSERT_TRUE(mgr);
+    EXPECT_EQ("forward-ddns", mgr->getName());
 
     // Verify that the forward manager has the correct number of domains.
     DdnsDomainMapPtr domains = mgr->getDomains();
@@ -578,6 +579,7 @@ TEST_F(D2CfgMgrTest, fullConfig) {
     // Verify that the reverse manager can be retrieved.
     mgr = context->getReverseMgr();
     ASSERT_TRUE(mgr);
+    EXPECT_EQ("reverse-ddns", mgr->getName());
 
     // Verify that the reverse manager has the correct number of domains.
     domains = mgr->getDomains();

+ 17 - 1
src/bin/d2/tests/d2_zone_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
 #include <config.h>
 #include <d2/d2_zone.h>
 #include <gtest/gtest.h>
+#include <sstream>
 
 using namespace std;
 using namespace isc;
@@ -41,6 +42,21 @@ TEST(D2ZoneTest, toText) {
     EXPECT_EQ("foo.example.com. IN SOA\n", zone2.toText());
 }
 
+  // Same than for toText() but using the << operator.
+TEST(D2ZoneTest, output) {
+    // Create first object.
+    D2Zone zone1(Name("example.com"), RRClass::ANY());
+    ostringstream ss;
+    ss << zone1;
+    EXPECT_EQ("example.com. ANY SOA\n", ss.str());
+    // Create another object with different parameters to make sure that the
+    // function's output changes accordingly.
+    D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+    ostringstream ss2;
+    ss2 << zone2;
+    EXPECT_EQ("foo.example.com. IN SOA\n", ss2.str());
+}
+
 // This test verifies that the equality and inequality operators behave as
 // expected.
 TEST(D2ZoneTest, compare) {

+ 76 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -624,6 +624,21 @@ TEST_F(Dhcp4ParserTest, bogusCommand) {
     EXPECT_THROW(parseDHCP4("{\"bogus\": 5}"), Dhcp4ParseError);
 }
 
+/// The goal of this test is to verify empty interface-config is accepted.
+TEST_F(Dhcp4ParserTest, emptyInterfaceConfig) {
+
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP4("{ \"rebind-timer\": 2000, "
+                                      "\"renew-timer\": 1000, "
+                                      "\"valid-lifetime\": 4000 }"));
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // returned value should be 0 (success)
+    checkResult(status, 0);
+}
+
 /// The goal of this test is to verify if wrongly defined subnet will
 /// be rejected. Properly defined subnet must include at least one
 /// pool definition.
@@ -4707,5 +4722,66 @@ TEST_F(Dhcp4ParserTest, pooMinMaxlUserContext) {
     EXPECT_EQ(true, bool_value);
 }
 
+// Test verifies the error message for an incorrect pool range
+// is what we expect.
+TEST_F(Dhcp4ParserTest, invalidPoolRange) {
+    string config = "{ " + genIfaceConfig() + ", \n" +
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet4\": [ {  \n"
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 19.2.0.200\" } ], \n"
+        "    \"subnet\": \"192.0.2.0/24\"  \n"
+        " } ] \n"
+        "} \n";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    int rcode;
+    ConstElementPtr comment = parseAnswer(rcode, status);
+    string text;
+    ASSERT_NO_THROW(text = comment->stringValue());
+
+    EXPECT_EQ(1, rcode);
+    string expected = "Failed to create pool defined by: "
+	"192.0.2.1-19.2.0.200 (<string>:6:26)";
+    EXPECT_EQ(expected, text);
+}
+
+// Test verifies the error message for an outside subnet pool range
+// is what we expect.
+TEST_F(Dhcp4ParserTest, outsideSubnetPool) {
+    string config = "{ " + genIfaceConfig() + ", \n" +
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet4\": [ {  \n"
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], \n"
+        "    \"subnet\": \"10.0.2.0/24\"  \n"
+        " } ] \n"
+        "} \n";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    int rcode;
+    ConstElementPtr comment = parseAnswer(rcode, status);
+    string text;
+    ASSERT_NO_THROW(text = comment->stringValue());
+
+    EXPECT_EQ(1, rcode);
+    string expected = "subnet configuration failed (<string>:5:14): "
+	"a pool of type V4, with the following address range: "
+	"192.0.2.1-192.0.2.100 does not match the prefix of a subnet: "
+	"10.0.2.0/24 to which it is being added";
+    EXPECT_EQ(expected, text);
+}
 
 }

+ 79 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -774,6 +774,22 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
     EXPECT_THROW(parseDHCP6("{\"bogus\": 5}"), Dhcp6ParseError);
 }
 
+/// The goal of this test is to verify empty interface-config is accepted.
+TEST_F(Dhcp6ParserTest, emptyInterfaceConfig) {
+
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP6("{ \"preferred-lifetime\": 3000,"
+                                      "\"rebind-timer\": 2000, "
+                                      "\"renew-timer\": 1000, "
+                                      "\"valid-lifetime\": 4000 }"));
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 0 (success)
+    checkResult(status, 0);
+}
+
 /// The goal of this test is to verify if configuration without any
 /// subnets defined can be accepted.
 TEST_F(Dhcp6ParserTest, emptySubnet) {
@@ -5197,5 +5213,68 @@ TEST_F(Dhcp6ParserTest, pdPoolUserContextlw4over6) {
     EXPECT_EQ(56L, int_value);
 }
 
+// Test verifies the error message for an incorrect pool range
+// is what we expect.
+TEST_F(Dhcp6ParserTest, invalidPoolRange) {
+    string config = "{ " + genIfaceConfig() + ", \n" +
+        "\"valid-lifetime\": 4000, \n"
+        "\"preferred-lifetime\": 3000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ {  \n"
+        "    \"pools\": [ { \"pool\": \"2001:db8:: - 200:1db8::ffff\" } ], \n"
+        "    \"subnet\": \"2001:db8::/32\"  \n"
+        " } ] \n"
+        "} \n";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP6(config, true));
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    int rcode;
+    ConstElementPtr comment = parseAnswer(rcode, status);
+    string text;
+    ASSERT_NO_THROW(text = comment->stringValue());
+
+    EXPECT_EQ(1, rcode);
+    string expected = "Failed to create pool defined by: "
+        "2001:db8::-200:1db8::ffff (<string>:7:26)";
+    EXPECT_EQ(expected, text);
+}
+
+// Test verifies the error message for an outside subnet pool range
+// is what we expect.
+TEST_F(Dhcp6ParserTest, outsideSubnetPool) {
+    string config = "{ " + genIfaceConfig() + ", \n" +
+        "\"valid-lifetime\": 4000, \n"
+        "\"preferred-lifetime\": 3000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ {  \n"
+        "    \"pools\": [ { \"pool\": \"2001:db8:: - 2001:db8::ffff\" } ], \n"
+        "    \"subnet\": \"2001:dc8::/32\"  \n"
+        " } ] \n"
+        "} \n";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP6(config, true));
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    int rcode;
+    ConstElementPtr comment = parseAnswer(rcode, status);
+    string text;
+    ASSERT_NO_THROW(text = comment->stringValue());
+
+    EXPECT_EQ(1, rcode);
+    string expected = "subnet configuration failed (<string>:6:14): "
+        "a pool of type IA_NA, with the following address range: "
+        "2001:db8::-2001:db8::ffff does not match the prefix of a subnet: "
+        "2001:dc8::/32 to which it is being added";
+    EXPECT_EQ(expected, text);
+}
 
 };

+ 4 - 3
src/bin/lfc/lfc_controller.cc

@@ -150,7 +150,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
 
     opterr = 0;
     optind = 1;
-    while ((ch = getopt(argc, argv, ":46dvVWp:x:i:o:c:f:")) != -1) {
+    while ((ch = getopt(argc, argv, ":46dhvVWp:x:i:o:c:f:")) != -1) {
         switch (ch) {
         case '4':
             // Process DHCPv4 lease files.
@@ -163,12 +163,12 @@ LFCController::parseArgs(int argc, char* argv[]) {
             break;
 
         case 'v':
-            // Print just Kea vesion and exit.
+            // Print just Kea version and exit.
             std::cout << getVersion(false) << std::endl;
             exit(EXIT_SUCCESS);
 
         case 'V':
-            // Print extended  Kea vesion and exit.
+            // Print extended  Kea version and exit.
             std::cout << getVersion(true) << std::endl;
             exit(EXIT_SUCCESS);
 
@@ -236,6 +236,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
 
         case '?':
             // Unknown argument
+            // note this will catch all the prevous ... name missing
             isc_throw(InvalidUsage, "Unknown argument");
 
         case ':':

+ 4 - 0
src/bin/lfc/tests/lfc_controller_unittests.cc

@@ -145,6 +145,8 @@ TEST_F(LFCControllerTest, initialValues) {
     EXPECT_TRUE(lfc_controller.getPidFile().empty());
 }
 
+/// @todo verify that parsing -v/V/W/h works well without ASSERT_EXIT
+
 /// @brief Verify that parsing a full command line works.
 /// Parse a complete command line then verify the parsed
 /// and saved data matches our expectations.
@@ -663,4 +665,6 @@ TEST_F(LFCControllerTest, launch6) {
     EXPECT_TRUE(noExistIOFP());
 }
 
+// @todo double launch (how to do that)
+
 } // end of anonymous namespace

+ 12 - 0
src/lib/dhcpsrv/tests/host_mgr_unittest.cc

@@ -388,6 +388,18 @@ TEST_F(HostMgrTest, get6ByPrefix) {
     testGet6ByPrefix(*getCfgHosts(), *getCfgHosts());
 }
 
+// This test verifies that without a host data source an exception is thrown.
+TEST_F(HostMgrTest, addNoDataSource) {
+    // Remove all configuration.
+    CfgMgr::instance().clear();
+    // Recreate HostMgr instance.
+    HostMgr::create();
+
+    HostPtr host(new Host(hwaddrs_[0]->toText(false), "hw-address",
+                          SubnetID(1), SubnetID(0), IOAddress("192.0.2.5")));
+    EXPECT_THROW(HostMgr::instance().add(host), NoHostDataSourceManager);
+}
+
 // The following tests require MySQL enabled.
 #if defined HAVE_MYSQL
 

+ 6 - 0
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -375,6 +375,12 @@ TEST_F(MemfileLeaseMgrTest, constructor) {
     EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
 }
 
+// Checks if there is no lease manager NoLeaseManager is thrown.
+TEST_F(MemfileLeaseMgrTest, noLeaseManager) {
+    LeaseMgrFactory::destroy();
+    EXPECT_THROW(LeaseMgrFactory::instance(), NoLeaseManager);
+}
+
 // Checks if the getType() and getName() methods both return "memfile".
 TEST_F(MemfileLeaseMgrTest, getTypeAndName) {
     startBackend(V4);

+ 3 - 1
src/lib/dns/tests/dns_exceptions_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -42,6 +42,7 @@ TEST(DNSExceptionsTest, checkExceptionsHierarchy) {
         const isc::dns::Exception& exception_cast2 =
           dynamic_cast<const isc::dns::Exception&>(exception);
         // to avoid compiler warning
+        exception_cast.getRcode();
         exception_cast.what();
         exception_cast2.what();
     });
@@ -53,6 +54,7 @@ TEST(DNSExceptionsTest, checkExceptionsHierarchy) {
         const isc::dns::Exception& exception_cast2 =
           dynamic_cast<const isc::dns::Exception&>(exception);
         // to avoid compiler warning
+        exception_cast.getRcode();
         exception_cast.what();
         exception_cast2.what();
     });

+ 12 - 1
src/lib/exceptions/tests/exceptions_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -36,6 +36,17 @@ TEST_F(ExceptionTest, basicMethods) {
     }
 }
 
+// Same than basicMethods but using a string (vs char *)
+TEST_F(ExceptionTest, string) {
+    try {
+        isc_throw(Exception, std::string(teststring));
+    } catch (Exception& ex) {
+        EXPECT_EQ(ex.getMessage(), std::string(teststring));
+        EXPECT_EQ(ex.getFile(), std::string(__FILE__));
+        EXPECT_EQ(ex.getLine(), __LINE__ - 4);
+    }
+}
+
 // Test to see if it works as a proper derived class of std::exception.
 TEST_F(ExceptionTest, stdInheritance) {
     try {

+ 7 - 0
src/lib/hooks/tests/library_manager_unittest.cc

@@ -128,6 +128,13 @@ public:
 };
 
 
+// Check that LibraryManager constructor requires a not null manager
+
+TEST_F(LibraryManagerTest, NullManager) {
+    EXPECT_THROW(PublicLibraryManager(std::string("foo"), 0, 0),
+                 NoCalloutManager);
+}
+
 // Check that openLibrary() reports an error when it can't find the specified
 // library.
 

+ 9 - 1
src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,6 +18,14 @@ using namespace isc::log::interprocess;
 using isc::util::unittests::parentReadState;
 
 namespace {
+TEST(InterprocessSyncFileTest, BadFile) {
+    InterprocessSyncFile sync("/no-such--dir/or--file");
+    InterprocessSyncLocker locker(sync);
+
+    EXPECT_FALSE(locker.isLocked());
+    ASSERT_THROW(locker.lock(), InterprocessSyncFileError);
+}
+
 TEST(InterprocessSyncFileTest, TestLock) {
     InterprocessSyncFile sync("test");
     InterprocessSyncLocker locker(sync);

+ 15 - 0
src/lib/util/tests/process_spawn_unittest.cc

@@ -90,6 +90,21 @@ bool waitForProcessFast(const ProcessSpawn& process, const pid_t pid,
     return (true);
 }
 
+// This test verifies that if the thread calling spawn has SIGCHLD
+// already block ProcessSpawnError is thrown (@todo the second error
+// case: fork() failling)
+TEST(ProcessSpawn, sigchldBlocked) {
+    std::vector<std::string> args;
+    ProcessSpawn process(getApp(), args);
+    sigset_t sset;
+    sigemptyset(&sset);
+    sigaddset(&sset, SIGCHLD);
+    sigset_t osset;
+    pthread_sigmask(SIG_BLOCK, &sset, &osset);
+    EXPECT_THROW(process.spawn(), ProcessSpawnError);
+    sigprocmask(SIG_SETMASK, &osset, 0);
+}
+
 // This test verifies that the external application can be ran with
 // arguments and that the exit code is gathered.
 TEST(ProcessSpawn, spawnWithArgs) {