// Copyright (C) 2016-2021 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 // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace isc; using namespace isc::asiolink; using namespace isc::dhcp; using namespace isc::util; namespace { /// @brief Type of the "copy" operation to be performed in a test. /// /// Possible operations are: /// - copy construction, /// - cloning with Option::clone, /// - assignment. enum OpType { COPY, CLONE, ASSIGN }; /// @brief Generic test for deep copy of an option. /// /// This test can use one of the three supported operations to deep copy /// an option: copy construction, cloning or assignment. /// /// After copying the option the following parameters checked if they /// have been copied (copied by the Option class): /// - universe, /// - option type, /// - encapsulated space, /// - data. /// /// This test also checks that the sub options have been copied by checking /// that: /// - options' types match, /// - binary representations are equal, /// - pointers to the options are unequal (to make sure that the option has /// been copied, rather than the pointer). /// /// @param op_type Copy operation to be performed. /// @param option Source option. /// @param option_copy Destination option. Note that this option may be /// initially set to a non-null value. For the "copy" and "clone" operations /// the pointer will be reset, so there is no sense to initialize this /// object to a non-null value. However, for the assignment testing it is /// recommended to initialize the option_copy to point to an option having /// different parameters to verify that all parameters have been overridden /// by the assignment operation. template void testCopyAssign(const OpType& op_type, boost::shared_ptr& option, boost::shared_ptr& option_copy) { // Set the encapsulated to 'foo' because tests usually don't set that // value. option->setEncapsulatedSpace("foo"); // Create two sub options of different types to later check that they // are copied. OptionUint16Ptr sub1 = OptionUint16Ptr(new OptionUint16(Option::V4, 10, 234)); Option4AddrLstPtr sub2 = Option4AddrLstPtr(new Option4AddrLst(11, IOAddress("192.0.2.3"))); option->addOption(sub1); option->addOption(sub2); // Copy option by copy construction, cloning or assignment. switch (op_type) { case COPY: option_copy.reset(new OptionType(*option)); break; case CLONE: option_copy = boost::dynamic_pointer_cast(option->clone()); ASSERT_TRUE(option_copy); break; case ASSIGN: option_copy->setEncapsulatedSpace("bar"); *option_copy = *option; break; default: ADD_FAILURE() << "unsupported operation"; return; } // Verify that basic parameters have been copied. EXPECT_EQ(option->getUniverse(), option_copy->getUniverse()); EXPECT_EQ(option->getType(), option_copy->getType()); EXPECT_EQ(option->len(), option_copy->len()); EXPECT_EQ(option->getEncapsulatedSpace(), option_copy->getEncapsulatedSpace()); EXPECT_TRUE(std::equal(option->getData().begin(), option->getData().end(), option_copy->getData().begin())); // Retrieve sub options so as they can be compared. const OptionCollection& option_subs = option->getOptions(); const OptionCollection& option_copy_subs = option_copy->getOptions(); ASSERT_EQ(option_subs.size(), option_copy_subs.size()); // Iterate over source options. OptionCollection::const_iterator it_copy = option_copy_subs.begin(); for (OptionCollection::const_iterator it = option_subs.begin(); it != option_subs.end(); ++it, ++it_copy) { // The option codes should be equal in both containers. EXPECT_EQ(it->first, it_copy->first); // Pointers must be unequal because the expectation is that options // are copied, rather than pointers. EXPECT_NE(it->second, it_copy->second); Option* opt_ptr = it->second.get(); Option* opt_copy_ptr = it_copy->second.get(); // The C++ types must match. EXPECT_TRUE(typeid(*opt_ptr) == typeid(*opt_copy_ptr)); } // Final check is to compare their binary representations. std::vector buf = option->toBinary(true); std::vector buf_copy = option_copy->toBinary(true); ASSERT_EQ(buf.size(), buf_copy.size()); EXPECT_TRUE(std::equal(buf_copy.begin(), buf_copy.end(), buf.begin())); } // **************************** Option *************************** /// @brief Test deep copy of option encapsulated by Option type. /// /// @param op_type Copy operation type. void testOption(const OpType& op_type) { OptionBuffer buf(10, 1); OptionPtr option(new Option(Option::V4, 1, buf)); OptionPtr option_copy(new Option(Option::V6, 1000)); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Save binary representation of the original option. We will // be later comparing it with a copied option to make sure that // modification of the original option doesn't affect the copy. std::vector binary_copy = option_copy->toBinary(true); // Modify the original option. OptionBuffer buf_modified(10, 2); option->setData(buf_modified.begin(), buf_modified.end()); // Retrieve the binary representation of the copy to verify that // it hasn't been modified. std::vector binary_copy_after = option_copy->toBinary(true); ASSERT_EQ(binary_copy.size(), binary_copy_after.size()); EXPECT_TRUE(std::equal(binary_copy_after.begin(), binary_copy_after.end(), binary_copy.begin())); } TEST(OptionCopyTest, optionConstructor) { testOption(COPY); } TEST(OptionCopyTest, optionClone) { testOption(CLONE); } TEST(OptionCopyTest, optionAssignment) { testOption(ASSIGN); } // **************************** OptionInt *************************** /// @brief Test deep copy of option encapsulated by OptionInt type. /// /// @param op_type Copy operation type. void testOptionInt(const OpType& op_type) { OptionUint16Ptr option(new OptionUint16(Option::V4, 1, 12345)); OptionUint16Ptr option_copy(new OptionUint16(Option::V6, 10, 11111)); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify value in the original option. option->setValue(9); // The value in the copy should not be affected. EXPECT_EQ(12345, option_copy->getValue()); } TEST(OptionCopyTest, optionIntConstructor) { testOptionInt(COPY); } TEST(OptionCopyTest, optionIntClone) { testOptionInt(CLONE); } TEST(OptionCopyTest, optionIntAssignment) { testOptionInt(ASSIGN); } // ************************* OptionIntArray *************************** /// @brief Test deep copy of option encapsulated by OptionIntArray type. /// /// @param op_type Copy operation type. void testOptionIntArray(const OpType& op_type) { OptionUint32ArrayPtr option(new OptionUint32Array(Option::V4, 1));; option->addValue(2345); option->addValue(3456); OptionUint32ArrayPtr option_copy(new OptionUint32Array(Option::V6, 10)); option_copy->addValue(5678); option_copy->addValue(6789); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the values in the original option. option->setValues(std::vector(2, 7)); // The values in the copy should not be affected. std::vector values_copy = option_copy->getValues(); ASSERT_EQ(2, values_copy.size()); EXPECT_EQ(2345, values_copy[0]); EXPECT_EQ(3456, values_copy[1]); } TEST(OptionCopyTest, optionIntArrayConstructor) { testOptionIntArray(COPY); } TEST(OptionCopyTest, optionIntArrayClone) { testOptionIntArray(CLONE); } TEST(OptionCopyTest, optionIntArrayAssignment) { testOptionIntArray(ASSIGN); } // ************************* Option4AddrLst *************************** /// @brief Test deep copy of option encapsulated by Option4AddrLst or /// Option6AddrLst type. /// /// @param op_type Copy operation type. /// @param option_address Address carried in the source option. /// @param option_copy_address Address carried in the destination option. /// @param option_modified_address Address to which the original address /// is modified to check that this modification doesn't affect option /// copy. /// @tparam OptionType Option4AddrLst or Option6AddrLst. template void testOptionAddrLst(const OpType& op_type, const IOAddress& option_address, const IOAddress& option_copy_address, const IOAddress& option_modified_address) { typedef boost::shared_ptr OptionTypePtr; OptionTypePtr option(new OptionType(1, option_address)); OptionTypePtr option_copy(new OptionType(10, option_copy_address)); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the address in the original option. option->setAddress(option_modified_address); // The address in the copy should not be affected. typename OptionType::AddressContainer addrs_copy = option_copy->getAddresses(); ASSERT_EQ(1, addrs_copy.size()); EXPECT_EQ(option_address.toText(), addrs_copy[0].toText()); } /// @brief Test deep copy of option encapsulated by Option4AddrLst type. /// /// @param op_type Copy operation type. void testOption4AddrLst(const OpType& op_type) { testOptionAddrLst(op_type, IOAddress("127.0.0.1"), IOAddress("192.0.2.111"), IOAddress("127.0.0.1")); } TEST(OptionCopyTest, option4AddrLstConstructor) { testOption4AddrLst(COPY); } TEST(OptionCopyTest, option4AddrLstClone) { testOption4AddrLst(CLONE); } TEST(OptionCopyTest, option4AddrLstAssignment) { testOption4AddrLst(ASSIGN); } // ************************* Option6AddrLst *************************** /// @brief Test deep copy of option encapsulated by Option6AddrLst type. /// /// @param op_type Copy operation type. void testOption6AddrLst(const OpType& op_type) { testOptionAddrLst(op_type, IOAddress("2001:db8:1::2"), IOAddress("3001::cafe"), IOAddress("3000:1::1")); } TEST(OptionCopyTest, option6AddrLstConstructor) { testOption6AddrLst(COPY); } TEST(OptionCopyTest, option6AddrLstClone) { testOption6AddrLst(CLONE); } TEST(OptionCopyTest, option6AddrLstAssignment) { testOption6AddrLst(ASSIGN); } // *************************** Option6IA *************************** /// @brief Test deep copy of option encapsulated by Option6IA type. /// /// @param op_type Copy operation type. void testOption6IA(const OpType& op_type) { Option6IAPtr option(new Option6IA(D6O_IA_NA, 1234)); option->setT1(1000); option->setT2(2000); Option6IAPtr option_copy(new Option6IA(D6O_IA_PD, 5678)); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the values in the original option. option->setT1(3000); option->setT2(4000); option->setIAID(5678); // The values in the copy should not be affected. EXPECT_EQ(1000, option_copy->getT1()); EXPECT_EQ(2000, option_copy->getT2()); EXPECT_EQ(1234, option_copy->getIAID()); } TEST(OptionCopyTest, option6IAConstructor) { testOption6IA(COPY); } TEST(OptionCopyTest, option6IAClone) { testOption6IA(CLONE); } TEST(OptionCopyTest, option6IAAssignment) { testOption6IA(ASSIGN); } // *************************** Option6IAAddr *************************** /// @brief Test deep copy of option encapsulated by Option6IAAddr type. /// /// @param op_type Copy operation type. void testOption6IAAddr(const OpType& op_type) { Option6IAAddrPtr option(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1::1"), 60, 90)); Option6IAAddrPtr option_copy(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1::2"), 50, 80)); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the values in the original option. option->setAddress(IOAddress("2001:db8:1::3")); option->setPreferred(1000); option->setValid(2000); // The values in the copy should not be affected. EXPECT_EQ("2001:db8:1::1", option_copy->getAddress().toText()); EXPECT_EQ(60, option_copy->getPreferred()); EXPECT_EQ(90, option_copy->getValid()); } TEST(OptionCopyTest, option6IAAddrConstructor) { testOption6IAAddr(COPY); } TEST(OptionCopyTest, option6IAAddrClone) { testOption6IAAddr(CLONE); } TEST(OptionCopyTest, option6IAAddrAssignment) { testOption6IAAddr(ASSIGN); } // *************************** Option6IAPrefix *************************** /// @brief Test deep copy of option encapsulated by Option6IAPrefix type. /// /// @param op_type Copy operation type. void testOption6IAPrefix(const OpType& op_type) { Option6IAPrefixPtr option(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("3000::"), 64, 60, 90)); Option6IAPrefixPtr option_copy(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("3001::"), 48, 50, 80)); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the values in the original option. option->setPrefix(IOAddress("3002::"), 32); option->setPreferred(1000); option->setValid(2000); // The values in the copy should not be affected. EXPECT_EQ("3000::", option_copy->getAddress().toText()); EXPECT_EQ(64, option_copy->getLength()); EXPECT_EQ(60, option_copy->getPreferred()); EXPECT_EQ(90, option_copy->getValid()); } TEST(OptionCopyTest, option6IAPrefixConstructor) { testOption6IAPrefix(COPY); } TEST(OptionCopyTest, option6IAPrefixClone) { testOption6IAPrefix(CLONE); } TEST(OptionCopyTest, option6IAPrefixAssignment) { testOption6IAPrefix(ASSIGN); } // *************************** Option6StatusCode *************************** /// @brief Test deep copy of option encapsulated by Option6StatusCode type. /// /// @param op_type Copy operation type. void testOption6StatusCode(const OpType& op_type) { Option6StatusCodePtr option(new Option6StatusCode(STATUS_NoBinding, "no binding")); Option6StatusCodePtr option_copy(new Option6StatusCode(STATUS_Success, "success")); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the values in the original option. option->setStatusCode(STATUS_NoAddrsAvail); option->setStatusMessage("foo"); // The values in the copy should not be affected. EXPECT_EQ(STATUS_NoBinding, option_copy->getStatusCode()); EXPECT_EQ("no binding", option_copy->getStatusMessage()); } TEST(OptionCopyTest, option6StatusCodeConstructor) { testOption6StatusCode(COPY); } TEST(OptionCopyTest, option6StatusCodeClone) { testOption6StatusCode(CLONE); } TEST(OptionCopyTest, option6StatusCodeAssignment) { testOption6StatusCode(ASSIGN); } // *************************** OptionString *************************** /// @brief Test deep copy of option encapsulated by OptionString type. /// /// @param op_type Copy operation type. void testOptionString(const OpType& op_type) { OptionStringPtr option(new OptionString(Option::V4, 1, "option value")); OptionStringPtr option_copy(new OptionString(Option::V6, 10, "another value")); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the string in the original option. option->setValue("foo"); // The string in the copy should not be affected. EXPECT_EQ("option value", option_copy->getValue()); } TEST(OptionCopyTest, optionStringConstructor) { testOptionString(COPY); } TEST(OptionCopyTest, optionStringClone) { testOptionString(CLONE); } TEST(OptionCopyTest, optionStringAssignment) { testOptionString(ASSIGN); } // *************************** OptionVendor *************************** /// @brief Test deep copy of option encapsulated by OptionVendor type. /// /// @param op_type Copy operation type. void testOptionVendor(const OpType& op_type) { OptionVendorPtr option(new OptionVendor(Option::V4, 2986)); OptionVendorPtr option_copy(new OptionVendor(Option::V6, 1111)); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the vendor id in the original option. option->setVendorId(2222); // The vendor id in the copy should not be affected. EXPECT_EQ(2986, option_copy->getVendorId()); } TEST(OptionCopyTest, optionVendorConstructor) { testOptionVendor(COPY); } TEST(OptionCopyTest, optionVendorClone) { testOptionVendor(CLONE); } TEST(OptionCopyTest, optionVendorAssignment) { testOptionVendor(ASSIGN); } // *********************** OptionVendorClass *************************** /// @brief Test deep copy of option encapsulated by OptionVendorClass type. /// /// @param op_type Copy operation type. void testOptionVendorClass(const OpType& op_type) { // Create a DHCPv4 option with a single tuple. OptionVendorClassPtr option(new OptionVendorClass(Option::V4, 2986)); OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); tuple = "vendor-class-value"; option->setTuple(0, tuple); // Create a DHCPv6 option with a single tuple. OptionVendorClassPtr option_copy(new OptionVendorClass(Option::V6, 1111)); OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES); tuple = "vendor-class-assigned"; option_copy->addTuple(tuple_copy); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the tuple in the original option and add one more tuple. tuple = "modified-vendor-class-value"; option->setTuple(0, tuple); tuple = "another-modified-vendor-class-value"; option->addTuple(tuple); // That change shouldn't affect the original option. It should still // contain a single tuple with the original value. ASSERT_EQ(1, option_copy->getTuplesNum()); tuple = option_copy->getTuple(0); EXPECT_TRUE(tuple.equals("vendor-class-value")); } TEST(OptionCopyTest, optionVendorClassConstructor) { testOptionVendorClass(COPY); } TEST(OptionCopyTest, optionVendorClassClone) { testOptionVendorClass(CLONE); } TEST(OptionCopyTest, optionVendorClassAssignment) { testOptionVendorClass(ASSIGN); } // ************************** Option4ClientFqdn *************************** /// @brief Test deep copy of option encapsulated by Option4ClientFqdn or /// Option6ClientFqdn type. /// /// @param op_type Copy operation type. /// @param option Option to be copied. /// @param option_copy Destination option. Note that this option may be /// initially set to a non-null value. For the "copy" and "clone" operations /// the pointer will be reset, so there is no sense to initialize this /// object to a non-null value. However, for the assignment testing it is /// recommended to initialize the option_copy to point to an option having /// different parameters to verify that all parameters have been overridden /// by the assignment operation. /// /// @tparam OptionType Option4ClientFqdn or Option6ClientFqdn. template void testOptionClientFqdn(const OpType& op_type, boost::shared_ptr& option, boost::shared_ptr& option_copy) { ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the values in the original option. option->setDomainName("newname", OptionType::PARTIAL); option->setFlag(OptionType::FLAG_S, false); option->setFlag(OptionType::FLAG_N, true); // Rcode is carried on the in the DHCPv4 Client FQDN option. // If the OptionType is pointing to a DHCPv6 option the dynamic // cast will result in NULL pointer and we'll not check the // RCODE. Option4ClientFqdnPtr option4 = boost::dynamic_pointer_cast(option); if (option4) { option4->setRcode(64); } // Verify that common parameters haven't been modified in the // copied option by the change in the original option. EXPECT_EQ("myname.example.org.", option_copy->getDomainName()); EXPECT_EQ(OptionType::FULL, option_copy->getDomainNameType()); EXPECT_TRUE(option_copy->getFlag(OptionType::FLAG_S)); EXPECT_FALSE(option_copy->getFlag(OptionType::FLAG_N)); // If we're dealing with DHCPv4 Client FQDN, we also need to // test RCODE. Option4ClientFqdnPtr option_copy4 = boost::dynamic_pointer_cast(option_copy); if (option_copy4) { EXPECT_EQ(255, option_copy4->getRcode().first.getCode()); EXPECT_EQ(255, option_copy4->getRcode().second.getCode()); } } /// @brief Test deep copy of option encapsulated by Option4ClientFqdn type. /// /// @param op_type Copy operation type. void testOption4ClientFqdn(const OpType& op_type) { Option4ClientFqdnPtr option(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S, Option4ClientFqdn::Rcode(255), "myname.example.org")); Option4ClientFqdnPtr option_copy(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O, Option4ClientFqdn::Rcode(0), "other.example.org")); ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn(op_type, option, option_copy)); } TEST(OptionCopyTest, option4ClientFqdnConstructor) { testOption4ClientFqdn(COPY); } TEST(OptionCopyTest, option4ClientFqdnClone) { testOption4ClientFqdn(CLONE); } TEST(OptionCopyTest, option4ClientFqdnAssignment) { testOption4ClientFqdn(ASSIGN); } // ************************** Option6ClientFqdn *************************** /// @brief Test deep copy of option encapsulated by Option6ClientFqdn type. /// /// @param op_type Copy operation type. void testOption6ClientFqdn(const OpType& op_type) { Option6ClientFqdnPtr option(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "myname.example.org")); Option6ClientFqdnPtr option_copy(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O, "other.example.org")); ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn(op_type, option, option_copy)); } TEST(OptionCopyTest, option6ClientFqdnConstructor) { testOption6ClientFqdn(COPY); } TEST(OptionCopyTest, option6ClientFqdnClone) { testOption6ClientFqdn(CLONE); } TEST(OptionCopyTest, option6ClientFqdnAssignment) { testOption6ClientFqdn(ASSIGN); } // **************************** OptionCustom *************************** /// @brief Test deep copy of option encapsulated by OptionCustom type. /// /// @param op_type Copy operation type. void testOptionCustom(const OpType& op_type) { // Create option with a single field carrying 16-bits integer. OptionDefinition def("foo", 1, "my-space", "uint16", true); OptionCustomPtr option(new OptionCustom(def, Option::V4)); option->addArrayDataField(5555); // Create option with two fields carrying IPv4 address and 32-bit // integer. OptionDefinition def_copy("bar", 10, "my-space", "record"); def_copy.addRecordField("ipv4-address"); def_copy.addRecordField("uint32"); OptionCustomPtr option_copy(new OptionCustom(def_copy, Option::V6)); option_copy->writeAddress(IOAddress("192.0.0.2")); option_copy->writeInteger(12, 1); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the original option value. option->writeInteger(1000); // The copied option should not be affected. ASSERT_EQ(1, option_copy->getDataFieldsNum()); EXPECT_EQ(5555, option_copy->readInteger()); } TEST(OptionCopyTest, optionCustomConstructor) { testOptionCustom(COPY); } TEST(OptionCopyTest, optionCustomClone) { testOptionCustom(CLONE); } TEST(OptionCopyTest, optionCustomAssignment) { testOptionCustom(ASSIGN); } // ************************ OptionOpaqueDataTuples *********************** /// @brief Test deep copy of option encapsulated by OptionOpaqueDataTuples type. /// /// @param op_type Copy operation type. void testOptionOpaqueDataTuples(const OpType& op_type) { OptionOpaqueDataTuplesPtr option(new OptionOpaqueDataTuples(Option::V4, 1)); OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE); tuple = "a string"; option->addTuple(tuple); tuple = "another string"; option->addTuple(tuple); OptionOpaqueDataTuplesPtr option_copy(new OptionOpaqueDataTuples(Option::V6, 10)); OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES); tuple_copy = "copy string"; option_copy->addTuple(tuple_copy); ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy)); // Modify the value in the first tuple and add one more tuple. tuple = "modified-first-tuple"; option->setTuple(0, tuple); tuple = "modified-second-tuple"; option->setTuple(1, tuple); // This should not affect the values in the original option. ASSERT_EQ(2, option_copy->getTuplesNum()); EXPECT_TRUE(option_copy->getTuple(0).equals("a string")); EXPECT_TRUE(option_copy->getTuple(1).equals("another string")); } TEST(OptionCopyTest, optionOpaqueDataTuplesConstructor) { testOptionOpaqueDataTuples(COPY); } TEST(OptionCopyTest, optionOpaqueDataTuplesClone) { testOptionOpaqueDataTuples(CLONE); } TEST(OptionCopyTest, optionOpaqueDataTuplesAssign) { testOptionOpaqueDataTuples(ASSIGN); } }