1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-12 10:04:14 +02:00

Simplify strIdFrom*() functions.

The strIdFrom*() forced the caller to pick an encoding, which led to a number of TRY...CATCH blocks in the code. In practice the caller does not care which encoding is used as long as the string is valid for some encoding.

Update the strIdFrom*() function to try all possible encodings and only throw an error when the string is not valid for any of them.
This commit is contained in:
David Steele 2021-11-01 10:08:56 -04:00 committed by GitHub
parent a92d793819
commit bc352fa6a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 107 additions and 132 deletions

View File

@ -15,6 +15,22 @@
<release-list>
<release date="XXXX-XX-XX" version="2.37dev" title="UNDER DEVELOPMENT">
<release-core-list>
<release-development-list>
<release-item>
<commit subject="Simplify strIdFrom*() functions.">
<github-pull-request id="1551"/>
</commit>
<release-item-contributor-list>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="reid.thompson"/>
</release-item-contributor-list>
<p>Add <code>StringId</code> as an option type.</p>
</release-item>
</release-development-list>
</release-core-list>
</release>
<release date="2021-11-01" version="2.36" title="Minor Bug Fixes and Improvements">

View File

@ -9,17 +9,7 @@ Build Common
String *
bldStrId(const char *const buffer)
{
StringId result = 0;
TRY_BEGIN()
{
result = strIdFromZ(stringIdBit5, buffer);
}
CATCH_ANY()
{
result = strIdFromZ(stringIdBit6, buffer);
}
TRY_END();
StringId result = strIdFromZ(buffer);
return strNewFmt("STRID%u(\"%s\", 0x%" PRIx64 ")", (unsigned int)(result & STRING_ID_BIT_MASK) + 5, buffer, result);
}

View File

@ -7,16 +7,25 @@ Represent Short Strings as Integers
#include "common/debug.h"
#include "common/type/stringId.h"
/***********************************************************************************************************************************
Number of bits to use for encoding. The number of bits affects the character set that can be encoded.
***********************************************************************************************************************************/
typedef enum
{
stringIdBit5 = 0, // 5-bit encoding for a-z, 2, 5, 6, and - characters
stringIdBit6 = 1, // 6-bit encoding for a-z, 0-9, A-Z, and - characters
} StringIdBit;
/***********************************************************************************************************************************
Constants used to extract information from the header
***********************************************************************************************************************************/
#define STRING_ID_BIT_MASK 3
#define STRING_ID_HEADER_SIZE 4
#define STRING_ID_PREFIX 4
/**********************************************************************************************************************************/
StringId
strIdFromZN(const StringIdBit bit, const char *const buffer, const size_t size)
// Helper to do encoding for specified number of bits
static StringId
strIdBitFromZN(const StringIdBit bit, const char *const buffer, const size_t size)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(ENUM, bit);
@ -61,7 +70,7 @@ strIdFromZN(const StringIdBit bit, const char *const buffer, const size_t size)
break;
if (map[(uint8_t)buffer[bufferIdx]] == 0)
THROW_FMT(FormatError, "'%c' is invalid for 5-bit encoding in '%s'", buffer[bufferIdx], buffer);
FUNCTION_TEST_RETURN(0);
}
// Set encoding in header
@ -149,7 +158,7 @@ strIdFromZN(const StringIdBit bit, const char *const buffer, const size_t size)
break;
if (map[(uint8_t)buffer[bufferIdx]] == 0)
THROW_FMT(FormatError, "'%c' is invalid for 6-bit encoding in '%s'", buffer[bufferIdx], buffer);
FUNCTION_TEST_RETURN(0);
}
// Set encoding in header
@ -200,6 +209,30 @@ strIdFromZN(const StringIdBit bit, const char *const buffer, const size_t size)
}
}
StringId
strIdFromZN(const char *const buffer, const size_t size, const bool error)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(VOID, buffer);
FUNCTION_TEST_PARAM(SIZE, size);
FUNCTION_TEST_PARAM(BOOL, error);
FUNCTION_TEST_END();
StringId result = strIdBitFromZN(stringIdBit5, buffer, size);
// If 5-bit encoding fails try 6-bit
if (result == 0)
{
result = strIdBitFromZN(stringIdBit6, buffer, size);
// Error when 6-bit encoding also fails
if (result == 0 && error)
THROW_FMT(FormatError, "'%s' contains invalid characters", buffer);
}
FUNCTION_TEST_RETURN(result);
}
/**********************************************************************************************************************************/
size_t
strIdToZN(StringId strId, char *const buffer)

View File

@ -50,15 +50,6 @@ StringId typedef to make them more recognizable in the code
***********************************************************************************************************************************/
typedef uint64_t StringId;
/***********************************************************************************************************************************
Number of bits to use for encoding. The number of bits affects the character set that can be encoded.
***********************************************************************************************************************************/
typedef enum
{
stringIdBit5 = 0, // 5-bit encoding for a-z, 2, 5, 6, and - characters
stringIdBit6 = 1, // 6-bit encoding for a-z, 0-9, A-Z, and - characters
} StringIdBit;
/***********************************************************************************************************************************
Macros to define constant StringIds. ALWAYS use bldStrId() to create these macros. The parameters in the macros are not verified
against each other so the str parameter is included only for documentation purposes.
@ -72,20 +63,20 @@ Functions
// Convert N chars to StringId, If the string is longer than the allowable length for the selected encoding then the StringID will
// be marked as "partial" and will have a '+' appended whenever it is converted back to a string. This is to distinguish it from a
// string with the same number of encoded characters that did not overflow.
StringId strIdFromZN(const StringIdBit bit, const char *const buffer, const size_t size);
StringId strIdFromZN(const char *buffer, size_t size, bool error);
// Convert String to StringId using strIdFromZN()
__attribute__((always_inline)) static inline StringId
strIdFromStr(const StringIdBit bit, const String *const str)
strIdFromStr(const String *const str)
{
return strIdFromZN(bit, strZ(str), strSize(str));
return strIdFromZN(strZ(str), strSize(str), true);
}
// Convert zero-terminted string to StringId using strIdFromZN()
// Convert zero-terminated string to StringId using strIdFromZN()
__attribute__((always_inline)) static inline StringId
strIdFromZ(const StringIdBit bit, const char *const str)
strIdFromZ(const char *const str)
{
return strIdFromZN(bit, str, strlen(str));
return strIdFromZN(str, strlen(str), true);
}
// Write StringId to characters without zero-terminating. The buffer at ptr must have enough space to write the entire StringId,

View File

@ -916,34 +916,6 @@ cfgOptionIdxStrNull(ConfigOption optionId, unsigned int optionIdx)
FUNCTION_LOG_RETURN_CONST(STRING, cfgOptionIdxInternal(optionId, optionIdx, cfgOptDataTypeString, true)->value.string);
}
// Helper to convert option String values to StringIds. Some options need 6-bit encoding while most work fine with 5-bit encoding.
// At some point the config parser will work with StringIds directly and this code can be removed, but for now it protects the
// callers from this logic and hopefully means no changes to the callers when the parser is updated.
static StringId
cfgOptionStrIdInternal(
const ConfigOption optionId, const unsigned int optionIdx)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(ENUM, optionId);
FUNCTION_TEST_PARAM(UINT, optionIdx);
FUNCTION_TEST_END();
const String *const value = cfgOptionIdxInternal(optionId, optionIdx, cfgOptDataTypeString, false)->value.string;
StringId result = 0;
TRY_BEGIN()
{
result = strIdFromStr(stringIdBit5, value);
}
CATCH_ANY()
{
result = strIdFromStr(stringIdBit6, value);
}
TRY_END();
FUNCTION_TEST_RETURN(result);
}
StringId
cfgOptionIdxStrId(ConfigOption optionId, unsigned int optionIdx)
{
@ -952,7 +924,10 @@ cfgOptionIdxStrId(ConfigOption optionId, unsigned int optionIdx)
FUNCTION_LOG_PARAM(UINT, optionIdx);
FUNCTION_LOG_END();
FUNCTION_LOG_RETURN(STRING_ID, cfgOptionStrIdInternal(optionId, optionIdx));
// At some point the config parser will work with StringIds directly and strIdFromStr() will be removed, but for now it protects
// the callers from this logic and hopefully means no changes to the callers when the parser is updated.
FUNCTION_LOG_RETURN(
STRING_ID, strIdFromStr(cfgOptionIdxInternal(optionId, optionIdx, cfgOptDataTypeString, false)->value.string));
}
/**********************************************************************************************************************************/

View File

@ -747,20 +747,8 @@ cfgParseOptionalFilterDepend(PackRead *const filter, const Config *const config,
// If a depend list exists, make sure the value is in the list
if (pckReadNext(filter))
{
StringId dependValueStrId = 0;
if (cfgParseOptionDataType(dependId) == cfgOptDataTypeString)
{
TRY_BEGIN()
{
dependValueStrId = strIdFromStr(stringIdBit5, dependValue->value.string);
}
CATCH_ANY()
{
dependValueStrId = strIdFromStr(stringIdBit6, dependValue->value.string);
}
TRY_END();
}
const StringId dependValueStrId = cfgParseOptionDataType(dependId) == cfgOptDataTypeString ?
strIdFromStr(dependValue->value.string) : 0;
do
{
@ -2338,36 +2326,14 @@ configParse(const Storage *storage, unsigned int argListSize, const char *argLis
if (parseRuleOption[optionId].type == cfgOptTypeString)
{
bool valueValid = true;
StringId value = 0;
const StringId value = strIdFromZN(strZ(valueAllow), strSize(valueAllow), false);
TRY_BEGIN()
while (pckReadNext(allowList))
{
TRY_BEGIN()
if (parseRuleValueStrId[pckReadU32P(allowList)] == value)
{
value = strIdFromStr(stringIdBit5, valueAllow);
}
CATCH_ANY()
{
value = strIdFromStr(stringIdBit6, valueAllow);
}
TRY_END();
}
CATCH_ANY()
{
valueValid = false;
}
TRY_END();
if (valueValid)
{
while (pckReadNext(allowList))
{
if (parseRuleValueStrId[pckReadU32P(allowList)] == value)
{
allowListFound = true;
break;
}
allowListFound = true;
break;
}
}
}

View File

@ -154,7 +154,7 @@ infoBackupLoadCallback(void *data, const String *section, const String *key, con
// When reading timestamps, read as uint64 to ensure always positive value (guarantee no backups before 1970)
.backupTimestampStart = (time_t)varUInt64(kvGet(backupKv, INFO_BACKUP_KEY_BACKUP_TIMESTAMP_START_VAR)),
.backupTimestampStop= (time_t)varUInt64(kvGet(backupKv, INFO_BACKUP_KEY_BACKUP_TIMESTAMP_STOP_VAR)),
.backupType = (BackupType)strIdFromStr(stringIdBit5, varStr(kvGet(backupKv, INFO_BACKUP_KEY_BACKUP_TYPE_VAR))),
.backupType = (BackupType)strIdFromStr(varStr(kvGet(backupKv, INFO_BACKUP_KEY_BACKUP_TYPE_VAR))),
// Possible NULL values
.backupArchiveStart = strDup(varStr(kvGet(backupKv, INFO_BACKUP_KEY_BACKUP_ARCHIVE_START_VAR))),

View File

@ -1865,7 +1865,7 @@ manifestLoadCallback(void *callbackData, const String *section, const String *ke
manifest->pub.data.backupTimestampStop = (time_t)varUInt64(value);
else if (strEq(key, MANIFEST_KEY_BACKUP_TYPE_STR))
{
manifest->pub.data.backupType = (BackupType)strIdFromStr(stringIdBit5, varStr(value));
manifest->pub.data.backupType = (BackupType)strIdFromStr(varStr(value));
ASSERT(
manifest->pub.data.backupType == backupTypeFull || manifest->pub.data.backupType == backupTypeDiff ||
manifest->pub.data.backupType == backupTypeIncr);

View File

@ -1322,7 +1322,7 @@ testRun(void)
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("report job error");
ProtocolParallelJob *job = protocolParallelJobNew(VARSTRDEF("key"), protocolCommandNew(strIdFromZ(stringIdBit5, "x")));
ProtocolParallelJob *job = protocolParallelJobNew(VARSTRDEF("key"), protocolCommandNew(strIdFromZ("x")));
protocolParallelJobErrorSet(job, errorTypeCode(&AssertError), STRDEF("error message"));
TEST_ERROR(backupJobResult((Manifest *)1, NULL, STRDEF("log"), strLstNew(), job, 0, 0), AssertError, "error message");
@ -1331,7 +1331,7 @@ testRun(void)
TEST_TITLE("report host/100% progress on noop result");
// Create job that skips file
job = protocolParallelJobNew(VARSTRDEF("pg_data/test"), protocolCommandNew(strIdFromZ(stringIdBit5, "x")));
job = protocolParallelJobNew(VARSTRDEF("pg_data/test"), protocolCommandNew(strIdFromZ("x")));
PackWrite *const resultPack = protocolPackNew();
pckWriteU32P(resultPack, backupCopyResultNoOp);

View File

@ -553,13 +553,13 @@ testRun(void)
#define TEST_STR5ID12 (TEST_STR5ID11 | (uint64_t)('6' - 24) << 59)
#define TEST_STR5ID13 (TEST_STR5ID12 | STRING_ID_PREFIX)
TEST_RESULT_UINT(strIdFromZN(stringIdBit5, "a", 1), TEST_STR5ID1, "5 bits 1 char");
TEST_RESULT_UINT(strIdFromZN(stringIdBit5, "abc-zk", 6), TEST_STR5ID6, "5 bits 6 chars");
TEST_RESULT_UINT(strIdFromZN(stringIdBit5, "abc-zkz2-y56", 12), TEST_STR5ID12, "5 bits 12 chars");
TEST_RESULT_UINT(strIdFromZN(stringIdBit5, "abc-zkz2-y56?", 13), TEST_STR5ID13, "5 bits 13 chars");
TEST_RESULT_UINT(strIdFromZN(stringIdBit5, "abc-zkz2-y56??", 14), TEST_STR5ID13, "5 bits 14 chars");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit5, "a", 1), TEST_STR5ID1, "5 bits 1 char");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit5, "abc-zk", 6), TEST_STR5ID6, "5 bits 6 chars");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit5, "abc-zkz2-y56", 12), TEST_STR5ID12, "5 bits 12 chars");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit5, "abc-zkz2-y56?", 13), TEST_STR5ID13, "5 bits 13 chars");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit5, "abc-zkz2-y56??", 14), TEST_STR5ID13, "5 bits 14 chars");
TEST_ERROR(strIdFromZN(stringIdBit5, "AB", 2), FormatError, "'A' is invalid for 5-bit encoding in 'AB'");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit5, "AB", 2), 0, "'A' is invalid for 5-bit encoding in 'AB'");
#define TEST_STR6ID1 (stringIdBit6 | (uint16_t)('a' - 96) << 4)
#define TEST_STR6ID2 (TEST_STR6ID1 | (uint16_t)('b' - 96) << 10)
@ -573,13 +573,13 @@ testRun(void)
#define TEST_STR6ID10 (TEST_STR6ID9 | (uint64_t)('9' - 20) << 58)
#define TEST_STR6ID11 (TEST_STR6ID10 | STRING_ID_PREFIX)
TEST_RESULT_UINT(strIdFromZN(stringIdBit6, "a", 1), TEST_STR6ID1, "6 bits 1 char");
TEST_RESULT_UINT(strIdFromZN(stringIdBit6, "abC-4", 5), TEST_STR6ID5, "6 bits 5 chars");
TEST_RESULT_UINT(strIdFromZN(stringIdBit6, "abC-40MzZ9", 10), TEST_STR6ID10, "6 bits 10 chars");
TEST_RESULT_UINT(strIdFromZN(stringIdBit6, "abC-40MzZ9?", 11), TEST_STR6ID11, "6 bits 11 chars");
TEST_RESULT_UINT(strIdFromZN(stringIdBit6, "abC-40MzZ9??", 12), TEST_STR6ID11, "6 bits 12 chars");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit6, "a", 1), TEST_STR6ID1, "6 bits 1 char");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit6, "abC-4", 5), TEST_STR6ID5, "6 bits 5 chars");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit6, "abC-40MzZ9", 10), TEST_STR6ID10, "6 bits 10 chars");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit6, "abC-40MzZ9?", 11), TEST_STR6ID11, "6 bits 11 chars");
TEST_RESULT_UINT(strIdBitFromZN(stringIdBit6, "abC-40MzZ9??", 12), TEST_STR6ID11, "6 bits 12 chars");
TEST_ERROR(strIdFromZN(stringIdBit6, "|B", 2), FormatError, "'|' is invalid for 6-bit encoding in '|B'");
TEST_RESULT_UINT(strIdFromZN("|B", 2, false), 0, "'|' is invalid for 6-bit encoding in '|B'");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("STRID*()");
@ -590,12 +590,17 @@ testRun(void)
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("strIdFromStr()");
TEST_RESULT_UINT(strIdFromStr(stringIdBit5, STRDEF("abc-")), TEST_STR5ID4, "5 bits 4 chars");
TEST_RESULT_UINT(strIdFromStr(STRDEF("abc-")), TEST_STR5ID4, "5 bits 4 chars");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("strIdFromZ()");
TEST_RESULT_UINT(strIdFromZ(stringIdBit6, "abC-"), TEST_STR6ID4, "6 bits 4 chars");
TEST_RESULT_UINT(strIdFromZ("abC-"), TEST_STR6ID4, "6 bits 4 chars");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error on invalid encoding");
TEST_ERROR(strIdFromZ("abc?"), FormatError, "'abc?' contains invalid characters");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("strIdToZN()");

View File

@ -1479,11 +1479,11 @@ testRun(void)
TEST_RESULT_BOOL(cfgOptionNegate(cfgOptConfig), true, "config is negated");
TEST_RESULT_INT(cfgOptionSource(cfgOptStanza), cfgSourceParam, "stanza is source param");
TEST_RESULT_STR_Z(cfgOptionStr(cfgOptStanza), "db", "stanza is set");
TEST_RESULT_UINT(cfgOptionStrId(cfgOptStanza), strIdFromZ(stringIdBit5, "db"), "stanza is set");
TEST_RESULT_UINT(cfgOptionStrId(cfgOptStanza), strIdFromZ("db"), "stanza is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptStanza), cfgSourceParam, "stanza is source param");
TEST_RESULT_STR_Z(cfgOptionIdxStr(cfgOptPgPath, 0), "/path/to/db", "pg1-path is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptPgPath), cfgSourceParam, "pg1-path is source param");
TEST_RESULT_UINT(cfgOptionIdxStrId(cfgOptRepoType, 0), strIdFromZ(stringIdBit6, "s3"), "repo-type is set");
TEST_RESULT_UINT(cfgOptionIdxStrId(cfgOptRepoType, 0), strIdFromZ("s3"), "repo-type is set");
TEST_RESULT_STR_Z(cfgOptionStr(cfgOptRepoS3Bucket), " test ", "repo1-s3-bucket is set and preserves spaces");
TEST_RESULT_STR_Z(cfgOptionStr(cfgOptRepoS3KeySecret), "xxx", "repo1-s3-secret is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptRepoS3KeySecret), cfgSourceConfig, "repo1-s3-secret is source env");

View File

@ -172,8 +172,7 @@ testRun(void)
driver.interface.infoList = storageTestPerfInfoList;
Storage *storageTest = storageNew(
strIdFromZ(stringIdBit6, "test"), STRDEF("/"), 0, 0, false, NULL, &driver, driver.interface);
Storage *storageTest = storageNew(strIdFromZ("test"), STRDEF("/"), 0, 0, false, NULL, &driver, driver.interface);
storageHelper.storageRepoWrite = memNew(sizeof(Storage *));
storageHelper.storageRepoWrite[0] = storageTest;

View File

@ -255,7 +255,7 @@ testRun(void)
driver.interface.infoList = storageTestManifestNewBuildInfoList;
const Storage *const storagePg = storageNew(
strIdFromZ(stringIdBit6, "test"), STRDEF("/pg"), 0, 0, false, NULL, &driver, driver.interface);
strIdFromZ("test"), STRDEF("/pg"), 0, 0, false, NULL, &driver, driver.interface);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("build manifest");

View File

@ -594,7 +594,7 @@ testRun(void)
TEST_TITLE("invalid command");
TEST_ERROR(
protocolClientExecute(client, protocolCommandNew(strIdFromZ(stringIdBit6, "BOGUS")), false), ProtocolError,
protocolClientExecute(client, protocolCommandNew(strIdFromZ("BOGUS")), false), ProtocolError,
"raised from test client: invalid command 'BOGUS' (0x38eacd271)");
// -----------------------------------------------------------------------------------------------------------------
@ -883,7 +883,7 @@ testRun(void)
{
TEST_ASSIGN(
job,
protocolParallelJobNew(VARSTRDEF("test"), protocolCommandNew(strIdFromZ(stringIdBit5, "c"))), "new job");
protocolParallelJobNew(VARSTRDEF("test"), protocolCommandNew(strIdFromZ("c"))), "new job");
TEST_RESULT_PTR(protocolParallelJobMove(job, memContextPrior()), job, "move job");
TEST_RESULT_PTR(protocolParallelJobMove(NULL, memContextPrior()), NULL, "move null job");
}
@ -915,7 +915,7 @@ testRun(void)
"local server 1");
// Command with output
TEST_RESULT_UINT(protocolServerCommandGet(server).id, strIdFromZ(stringIdBit5, "c-one"), "c-one command get");
TEST_RESULT_UINT(protocolServerCommandGet(server).id, strIdFromZ("c-one"), "c-one command get");
// Wait for notify from parent
HRN_FORK_CHILD_NOTIFY_GET();
@ -938,7 +938,7 @@ testRun(void)
"local server 2");
// Command with output
TEST_RESULT_UINT(protocolServerCommandGet(server).id, strIdFromZ(stringIdBit5, "c2"), "c2 command get");
TEST_RESULT_UINT(protocolServerCommandGet(server).id, strIdFromZ("c2"), "c2 command get");
// Wait for notify from parent
HRN_FORK_CHILD_NOTIFY_GET();
@ -947,7 +947,7 @@ testRun(void)
TEST_RESULT_VOID(protocolServerDataEndPut(server), "data end put");
// Command with error
TEST_RESULT_UINT(protocolServerCommandGet(server).id, strIdFromZ(stringIdBit5, "c-three"), "c-three command get");
TEST_RESULT_UINT(protocolServerCommandGet(server).id, strIdFromZ("c-three"), "c-three command get");
TEST_RESULT_VOID(protocolServerError(server, 39, STRDEF("very serious error"), STRDEF("stack")), "error put");
// Wait for exit
@ -993,20 +993,20 @@ testRun(void)
// -----------------------------------------------------------------------------------------------------------------
TEST_TITLE("add jobs");
ProtocolCommand *command = protocolCommandNew(strIdFromZ(stringIdBit5, "c-one"));
ProtocolCommand *command = protocolCommandNew(strIdFromZ("c-one"));
pckWriteStrP(protocolCommandParam(command), STRDEF("param1"));
pckWriteStrP(protocolCommandParam(command), STRDEF("param2"));
ProtocolParallelJob *job = protocolParallelJobNew(varNewStr(STRDEF("job1")), command);
TEST_RESULT_VOID(lstAdd(data.jobList, &job), "add job");
command = protocolCommandNew(strIdFromZ(stringIdBit5, "c2"));
command = protocolCommandNew(strIdFromZ("c2"));
pckWriteStrP(protocolCommandParam(command), STRDEF("param1"));
job = protocolParallelJobNew(varNewStr(STRDEF("job2")), command);
TEST_RESULT_VOID(lstAdd(data.jobList, &job), "add job");
command = protocolCommandNew(strIdFromZ(stringIdBit5, "c-three"));
command = protocolCommandNew(strIdFromZ("c-three"));
pckWriteStrP(protocolCommandParam(command), STRDEF("param1"));
job = protocolParallelJobNew(varNewStr(STRDEF("job3")), command);