Merge pull request #11093 from NixOS/fix-concatStringsSep

Fix C++ `concatStringsSep`
This commit is contained in:
Robert Hensing 2024-07-14 13:11:29 +02:00 committed by GitHub
commit 9d7397c4ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 275 additions and 25 deletions

View file

@ -16,6 +16,7 @@
#include "serialise.hh" #include "serialise.hh"
#include "build-result.hh" #include "build-result.hh"
#include "store-api.hh" #include "store-api.hh"
#include "strings.hh"
#include "derivations.hh" #include "derivations.hh"
#include "local-store.hh" #include "local-store.hh"
#include "legacy.hh" #include "legacy.hh"

View file

@ -1,3 +1,5 @@
#include <nlohmann/json.hpp>
#include "command.hh" #include "command.hh"
#include "markdown.hh" #include "markdown.hh"
#include "store-api.hh" #include "store-api.hh"
@ -6,8 +8,7 @@
#include "nixexpr.hh" #include "nixexpr.hh"
#include "profiles.hh" #include "profiles.hh"
#include "repl.hh" #include "repl.hh"
#include "strings.hh"
#include <nlohmann/json.hpp>
extern char * * environ __attribute__((weak)); extern char * * environ __attribute__((weak));

View file

@ -27,6 +27,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings-inline.hh"
namespace nix { namespace nix {
void completeFlakeInputPath( void completeFlakeInputPath(
@ -374,6 +376,7 @@ void completeFlakeRefWithFragment(
auto attrPath2 = (*attr)->getAttrPath(attr2); auto attrPath2 = (*attr)->getAttrPath(attr2);
/* Strip the attrpath prefix. */ /* Strip the attrpath prefix. */
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size()); attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
// FIXME: handle names with dots
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2))); completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
} }
} }

View file

@ -33,6 +33,8 @@
#include <gc/gc_cpp.h> #include <gc/gc_cpp.h>
#endif #endif
#include "strings.hh"
namespace nix { namespace nix {
/** /**

View file

@ -225,7 +225,7 @@ struct AttrDb
(key.first) (key.first)
(symbols[key.second]) (symbols[key.second])
(AttrType::ListOfStrings) (AttrType::ListOfStrings)
(concatStringsSep("\t", l)).exec(); (dropEmptyInitThenConcatStringsSep("\t", l)).exec();
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
@ -435,12 +435,12 @@ std::vector<Symbol> AttrCursor::getAttrPath(Symbol name) const
std::string AttrCursor::getAttrPathStr() const std::string AttrCursor::getAttrPathStr() const
{ {
return concatStringsSep(".", root->state.symbols.resolve(getAttrPath())); return dropEmptyInitThenConcatStringsSep(".", root->state.symbols.resolve(getAttrPath()));
} }
std::string AttrCursor::getAttrPathStr(Symbol name) const std::string AttrCursor::getAttrPathStr(Symbol name) const
{ {
return concatStringsSep(".", root->state.symbols.resolve(getAttrPath(name))); return dropEmptyInitThenConcatStringsSep(".", root->state.symbols.resolve(getAttrPath(name)));
} }
Value & AttrCursor::forceValue() Value & AttrCursor::forceValue()

View file

@ -49,6 +49,8 @@
#endif #endif
#include "strings-inline.hh"
using json = nlohmann::json; using json = nlohmann::json;
namespace nix { namespace nix {

View file

@ -7,6 +7,8 @@
#include <cstdlib> #include <cstdlib>
#include <sstream> #include <sstream>
#include "strings-inline.hh"
namespace nix { namespace nix {
unsigned long Expr::nrExprs = 0; unsigned long Expr::nrExprs = 0;

View file

@ -41,6 +41,11 @@ public:
} }
friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol); friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol);
bool empty() const
{
return s->empty();
}
}; };
/** /**

View file

@ -47,7 +47,7 @@ void ConfigFile::apply(const Settings & flakeSettings)
else if (auto* b = std::get_if<Explicit<bool>>(&value)) else if (auto* b = std::get_if<Explicit<bool>>(&value))
valueS = b->t ? "true" : "false"; valueS = b->t ? "true" : "false";
else if (auto ss = std::get_if<std::vector<std::string>>(&value)) else if (auto ss = std::get_if<std::vector<std::string>>(&value))
valueS = concatStringsSep(" ", *ss); // FIXME: evil valueS = dropEmptyInitThenConcatStringsSep(" ", *ss); // FIXME: evil
else else
assert(false); assert(false);

View file

@ -9,6 +9,8 @@
#include <iterator> #include <iterator>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings.hh"
namespace nix::flake { namespace nix::flake {
static FlakeRef getFlakeRef( static FlakeRef getFlakeRef(

View file

@ -23,6 +23,7 @@
#include <openssl/crypto.h> #include <openssl/crypto.h>
#include "exit.hh" #include "exit.hh"
#include "strings.hh"
namespace nix { namespace nix {

View file

@ -32,6 +32,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings.hh"
namespace nix { namespace nix {
DerivationGoal::DerivationGoal(const StorePath & drvPath, DerivationGoal::DerivationGoal(const StorePath & drvPath,

View file

@ -4,6 +4,7 @@
# include "derivation-goal.hh" # include "derivation-goal.hh"
#endif #endif
#include "local-store.hh" #include "local-store.hh"
#include "strings.hh"
namespace nix { namespace nix {

View file

@ -10,6 +10,8 @@
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings-inline.hh"
namespace nix { namespace nix {
std::optional<StorePath> DerivationOutput::path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const std::optional<StorePath> DerivationOutput::path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const

View file

@ -35,6 +35,8 @@
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
#include "strings.hh"
namespace nix { namespace nix {
@ -82,7 +84,7 @@ Settings::Settings()
Strings ss; Strings ss;
for (auto & p : tokenizeString<Strings>(*s, ":")) for (auto & p : tokenizeString<Strings>(*s, ":"))
ss.push_back("@" + p); ss.push_back("@" + p);
builders = concatStringsSep(" ", ss); builders = concatStringsSep("\n", ss);
} }
#if defined(__linux__) && defined(SANDBOX_SHELL) #if defined(__linux__) && defined(SANDBOX_SHELL)

View file

@ -51,6 +51,8 @@
#include <sqlite3.h> #include <sqlite3.h>
#include "strings.hh"
namespace nix { namespace nix {

View file

@ -10,6 +10,7 @@
#include "callback.hh" #include "callback.hh"
#include "closure.hh" #include "closure.hh"
#include "filetransfer.hh" #include "filetransfer.hh"
#include "strings.hh"
namespace nix { namespace nix {

View file

@ -7,6 +7,8 @@
#include <sqlite3.h> #include <sqlite3.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings.hh"
namespace nix { namespace nix {
static const char * schema = R"sql( static const char * schema = R"sql(

View file

@ -1,6 +1,7 @@
#include "globals.hh" #include "globals.hh"
#include "nar-info.hh" #include "nar-info.hh"
#include "store-api.hh" #include "store-api.hh"
#include "strings.hh"
namespace nix { namespace nix {

View file

@ -5,6 +5,7 @@
#include "regex-combinators.hh" #include "regex-combinators.hh"
#include "outputs-spec.hh" #include "outputs-spec.hh"
#include "path-regex.hh" #include "path-regex.hh"
#include "strings-inline.hh"
namespace nix { namespace nix {

View file

@ -4,6 +4,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "json-utils.hh" #include "json-utils.hh"
#include "comparator.hh" #include "comparator.hh"
#include "strings.hh"
namespace nix { namespace nix {

View file

@ -171,6 +171,9 @@ struct ValidPathInfo : UnkeyedValidPathInfo {
*/ */
bool checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const; bool checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const;
/**
* References as store path basenames, including a self reference if it has one.
*/
Strings shortRefs() const; Strings shortRefs() const;
ValidPathInfo(const ValidPathInfo & other) = default; ValidPathInfo(const ValidPathInfo & other) = default;

View file

@ -1,7 +1,9 @@
#include <regex>
#include "path-with-outputs.hh" #include "path-with-outputs.hh"
#include "store-api.hh" #include "store-api.hh"
#include "strings.hh"
#include <regex>
namespace nix { namespace nix {

View file

@ -22,6 +22,8 @@
#include <filesystem> #include <filesystem>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings.hh"
using json = nlohmann::json; using json = nlohmann::json;
namespace nix { namespace nix {

View file

@ -3,6 +3,7 @@
#include "hook-instance.hh" #include "hook-instance.hh"
#include "file-system.hh" #include "file-system.hh"
#include "child.hh" #include "child.hh"
#include "strings.hh"
namespace nix { namespace nix {

View file

@ -64,6 +64,8 @@
#include <grp.h> #include <grp.h>
#include <iostream> #include <iostream>
#include "strings.hh"
namespace nix { namespace nix {
void handleDiffHook( void handleDiffHook(

View file

@ -1,6 +1,7 @@
#include "canon-path.hh" #include "canon-path.hh"
#include "util.hh" #include "util.hh"
#include "file-path-impl.hh" #include "file-path-impl.hh"
#include "strings-inline.hh"
namespace nix { namespace nix {

View file

@ -9,6 +9,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings.hh"
namespace nix { namespace nix {
Config::Config(StringMap initials) Config::Config(StringMap initials)
@ -114,7 +116,7 @@ static void parseConfigFiles(const std::string & contents, const std::string & p
if (tokens.empty()) continue; if (tokens.empty()) continue;
if (tokens.size() < 2) if (tokens.size() < 2)
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path); throw UsageError("syntax error in configuration line '%1%' in '%2%'", line, path);
auto include = false; auto include = false;
auto ignoreMissing = false; auto ignoreMissing = false;
@ -127,7 +129,7 @@ static void parseConfigFiles(const std::string & contents, const std::string & p
if (include) { if (include) {
if (tokens.size() != 2) if (tokens.size() != 2)
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path); throw UsageError("syntax error in configuration line '%1%' in '%2%'", line, path);
auto p = absPath(tokens[1], dirOf(path)); auto p = absPath(tokens[1], dirOf(path));
if (pathExists(p)) { if (pathExists(p)) {
try { try {
@ -143,7 +145,7 @@ static void parseConfigFiles(const std::string & contents, const std::string & p
} }
if (tokens[1] != "=") if (tokens[1] != "=")
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path); throw UsageError("syntax error in configuration line '%1%' in '%2%'", line, path);
std::string name = std::move(tokens[0]); std::string name = std::move(tokens[0]);

View file

@ -23,6 +23,8 @@
# include <io.h> # include <io.h>
#endif #endif
#include "strings-inline.hh"
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace nix { namespace nix {

View file

@ -148,6 +148,7 @@ sources = files(
'signature/signer.cc', 'signature/signer.cc',
'source-accessor.cc', 'source-accessor.cc',
'source-path.cc', 'source-path.cc',
'strings.cc',
'suggestions.cc', 'suggestions.cc',
'tarfile.cc', 'tarfile.cc',
'terminal.cc', 'terminal.cc',
@ -215,6 +216,8 @@ headers = [config_h] + files(
'source-accessor.hh', 'source-accessor.hh',
'source-path.hh', 'source-path.hh',
'split.hh', 'split.hh',
'strings.hh',
'strings-inline.hh',
'suggestions.hh', 'suggestions.hh',
'sync.hh', 'sync.hh',
'tarfile.hh', 'tarfile.hh',

View file

@ -0,0 +1,31 @@
#pragma once
#include "strings.hh"
namespace nix {
template<class C>
std::string concatStringsSep(const std::string_view sep, const C & ss)
{
size_t size = 0;
bool tail = false;
// need a cast to string_view since this is also called with Symbols
for (const auto & s : ss) {
if (tail)
size += sep.size();
size += std::string_view(s).size();
tail = true;
}
std::string s;
s.reserve(size);
tail = false;
for (auto & i : ss) {
if (tail)
s += sep;
s += i;
tail = true;
}
return s;
}
} // namespace nix

19
src/libutil/strings.cc Normal file
View file

@ -0,0 +1,19 @@
#include <string>
#include "strings-inline.hh"
#include "util.hh"
namespace nix {
template std::string concatStringsSep(std::string_view, const Strings &);
template std::string concatStringsSep(std::string_view, const StringSet &);
template std::string concatStringsSep(std::string_view, const std::vector<std::string> &);
typedef std::string_view strings_2[2];
template std::string concatStringsSep(std::string_view, const strings_2 &);
typedef std::string_view strings_3[3];
template std::string concatStringsSep(std::string_view, const strings_3 &);
typedef std::string_view strings_4[4];
template std::string concatStringsSep(std::string_view, const strings_4 &);
} // namespace nix

21
src/libutil/strings.hh Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <list>
#include <set>
#include <string_view>
#include <string>
#include <vector>
namespace nix {
/**
* Concatenate the given strings with a separator between the elements.
*/
template<class C>
std::string concatStringsSep(const std::string_view sep, const C & ss);
extern template std::string concatStringsSep(std::string_view, const std::list<std::string> &);
extern template std::string concatStringsSep(std::string_view, const std::set<std::string> &);
extern template std::string concatStringsSep(std::string_view, const std::vector<std::string> &);
}

View file

@ -11,6 +11,8 @@
#include <sstream> #include <sstream>
#include <optional> #include <optional>
#include "strings.hh"
namespace nix { namespace nix {
void initLibUtil(); void initLibUtil();
@ -33,13 +35,26 @@ template<class C> C tokenizeString(std::string_view s, std::string_view separato
/** /**
* Concatenate the given strings with a separator between the * Ignore any empty strings at the start of the list, and then concatenate the
* elements. * given strings with a separator between the elements.
*
* @deprecated This function exists for historical reasons. You probably just
* want to use `concatStringsSep`.
*/ */
template<class C> template<class C>
std::string concatStringsSep(const std::string_view sep, const C & ss) [[deprecated("Consider removing the empty string dropping behavior. If acceptable, use concatStringsSep instead.")]]
std::string dropEmptyInitThenConcatStringsSep(const std::string_view sep, const C & ss)
{ {
size_t size = 0; size_t size = 0;
// TODO? remove to make sure we don't rely on the empty item ignoring behavior,
// or just get rid of this function by understanding the remaining calls.
// for (auto & i : ss) {
// // Make sure we don't rely on the empty item ignoring behavior
// assert(!i.empty());
// break;
// }
// need a cast to string_view since this is also called with Symbols // need a cast to string_view since this is also called with Symbols
for (const auto & s : ss) size += sep.size() + std::string_view(s).size(); for (const auto & s : ss) size += sep.size() + std::string_view(s).size();
std::string s; std::string s;

View file

@ -19,6 +19,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <algorithm> #include <algorithm>
#include "strings.hh"
using namespace nix; using namespace nix;
struct DevelopSettings : Config struct DevelopSettings : Config

View file

@ -6,6 +6,8 @@
#include <regex> #include <regex>
#include "strings.hh"
namespace nix { namespace nix {
struct Info struct Info

View file

@ -3,6 +3,7 @@
#include "command.hh" #include "command.hh"
#include "run.hh" #include "run.hh"
#include "strings.hh"
using namespace nix; using namespace nix;
@ -92,6 +93,7 @@ struct CmdShell : InstallablesCommand, MixEnvironment
} }
} }
// TODO: split losslessly; empty means .
auto unixPath = tokenizeString<Strings>(getEnv("PATH").value_or(""), ":"); auto unixPath = tokenizeString<Strings>(getEnv("PATH").value_or(""), ":");
unixPath.insert(unixPath.begin(), pathAdditions.begin(), pathAdditions.end()); unixPath.insert(unixPath.begin(), pathAdditions.begin(), pathAdditions.end());
auto unixPathString = concatStringsSep(":", unixPath); auto unixPathString = concatStringsSep(":", unixPath);

View file

@ -21,6 +21,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <iomanip> #include <iomanip>
#include "strings-inline.hh"
using namespace nix; using namespace nix;
using namespace nix::flake; using namespace nix::flake;
using json = nlohmann::json; using json = nlohmann::json;
@ -802,6 +804,7 @@ struct CmdFlakeCheck : FlakeCommand
throw Error("some errors were encountered during the evaluation"); throw Error("some errors were encountered during the evaluation");
if (!omittedSystems.empty()) { if (!omittedSystems.empty()) {
// TODO: empty system is not visible; render all as nix strings?
warn( warn(
"The check omitted these incompatible systems: %s\n" "The check omitted these incompatible systems: %s\n"
"Use '--all-systems' to check all.", "Use '--all-systems' to check all.",

View file

@ -41,6 +41,8 @@ extern std::string chrootHelperName;
void chrootHelper(int argc, char * * argv); void chrootHelper(int argc, char * * argv);
#endif #endif
#include "strings.hh"
namespace nix { namespace nix {
enum struct AliasStatus { enum struct AliasStatus {

View file

@ -9,6 +9,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings.hh"
using namespace nix; using namespace nix;
using nlohmann::json; using nlohmann::json;

View file

@ -17,6 +17,8 @@
#include <regex> #include <regex>
#include <iomanip> #include <iomanip>
#include "strings.hh"
using namespace nix; using namespace nix;
struct ProfileElementSource struct ProfileElementSource
@ -58,7 +60,7 @@ struct ProfileElement
StringSet names; StringSet names;
for (auto & path : storePaths) for (auto & path : storePaths)
names.insert(DrvName(path.name()).name); names.insert(DrvName(path.name()).name);
return concatStringsSep(", ", names); return dropEmptyInitThenConcatStringsSep(", ", names);
} }
/** /**

View file

@ -15,6 +15,8 @@
#include <fstream> #include <fstream>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "strings.hh"
using namespace nix; using namespace nix;
using json = nlohmann::json; using json = nlohmann::json;

View file

@ -26,9 +26,9 @@ static UnkeyedValidPathInfo makeEmpty()
}; };
} }
static UnkeyedValidPathInfo makeFull(const Store & store, bool includeImpureInfo) static ValidPathInfo makeFullKeyed(const Store & store, bool includeImpureInfo)
{ {
UnkeyedValidPathInfo info = ValidPathInfo { ValidPathInfo info = ValidPathInfo {
store, store,
"foo", "foo",
FixedOutputInfo { FixedOutputInfo {
@ -57,6 +57,9 @@ static UnkeyedValidPathInfo makeFull(const Store & store, bool includeImpureInfo
} }
return info; return info;
} }
static UnkeyedValidPathInfo makeFull(const Store & store, bool includeImpureInfo) {
return makeFullKeyed(store, includeImpureInfo);
}
#define JSON_TEST(STEM, OBJ, PURE) \ #define JSON_TEST(STEM, OBJ, PURE) \
TEST_F(PathInfoTest, PathInfo_ ## STEM ## _from_json) { \ TEST_F(PathInfoTest, PathInfo_ ## STEM ## _from_json) { \
@ -86,4 +89,13 @@ JSON_TEST(empty_impure, makeEmpty(), true)
JSON_TEST(pure, makeFull(*store, false), false) JSON_TEST(pure, makeFull(*store, false), false)
JSON_TEST(impure, makeFull(*store, true), true) JSON_TEST(impure, makeFull(*store, true), true)
TEST_F(PathInfoTest, PathInfo_full_shortRefs) {
ValidPathInfo it = makeFullKeyed(*store, true);
// it.references = unkeyed.references;
auto refs = it.shortRefs();
ASSERT_EQ(refs.size(), 2);
ASSERT_EQ(*refs.begin(), "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar");
ASSERT_EQ(*++refs.begin(), "n5wkd9frr45pa74if5gpz9j7mifg27fh-foo");
} }
} // namespace nix

View file

@ -15,7 +15,7 @@ struct RewriteParams {
strRewrites.insert(from + "->" + to); strRewrites.insert(from + "->" + to);
return os << return os <<
"OriginalString: " << bar.originalString << std::endl << "OriginalString: " << bar.originalString << std::endl <<
"Rewrites: " << concatStringsSep(",", strRewrites) << std::endl << "Rewrites: " << dropEmptyInitThenConcatStringsSep(",", strRewrites) << std::endl <<
"Expected result: " << bar.finalString; "Expected result: " << bar.finalString;
} }
}; };

View file

@ -0,0 +1,83 @@
#include <gtest/gtest.h>
#include "strings.hh"
namespace nix {
using Strings = std::vector<std::string>;
/* ----------------------------------------------------------------------------
* concatStringsSep
* --------------------------------------------------------------------------*/
TEST(concatStringsSep, empty)
{
Strings strings;
ASSERT_EQ(concatStringsSep(",", strings), "");
}
TEST(concatStringsSep, justOne)
{
Strings strings;
strings.push_back("this");
ASSERT_EQ(concatStringsSep(",", strings), "this");
}
TEST(concatStringsSep, emptyString)
{
Strings strings;
strings.push_back("");
ASSERT_EQ(concatStringsSep(",", strings), "");
}
TEST(concatStringsSep, emptyStrings)
{
Strings strings;
strings.push_back("");
strings.push_back("");
ASSERT_EQ(concatStringsSep(",", strings), ",");
}
TEST(concatStringsSep, threeEmptyStrings)
{
Strings strings;
strings.push_back("");
strings.push_back("");
strings.push_back("");
ASSERT_EQ(concatStringsSep(",", strings), ",,");
}
TEST(concatStringsSep, buildCommaSeparatedString)
{
Strings strings;
strings.push_back("this");
strings.push_back("is");
strings.push_back("great");
ASSERT_EQ(concatStringsSep(",", strings), "this,is,great");
}
TEST(concatStringsSep, buildStringWithEmptySeparator)
{
Strings strings;
strings.push_back("this");
strings.push_back("is");
strings.push_back("great");
ASSERT_EQ(concatStringsSep("", strings), "thisisgreat");
}
TEST(concatStringsSep, buildSingleString)
{
Strings strings;
strings.push_back("this");
ASSERT_EQ(concatStringsSep(",", strings), "this");
}
} // namespace nix

View file

@ -227,32 +227,32 @@ namespace nix {
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* concatStringsSep * dropEmptyInitThenConcatStringsSep
* --------------------------------------------------------------------------*/ * --------------------------------------------------------------------------*/
TEST(concatStringsSep, buildCommaSeparatedString) { TEST(dropEmptyInitThenConcatStringsSep, buildCommaSeparatedString) {
Strings strings; Strings strings;
strings.push_back("this"); strings.push_back("this");
strings.push_back("is"); strings.push_back("is");
strings.push_back("great"); strings.push_back("great");
ASSERT_EQ(concatStringsSep(",", strings), "this,is,great"); ASSERT_EQ(dropEmptyInitThenConcatStringsSep(",", strings), "this,is,great");
} }
TEST(concatStringsSep, buildStringWithEmptySeparator) { TEST(dropEmptyInitThenConcatStringsSep, buildStringWithEmptySeparator) {
Strings strings; Strings strings;
strings.push_back("this"); strings.push_back("this");
strings.push_back("is"); strings.push_back("is");
strings.push_back("great"); strings.push_back("great");
ASSERT_EQ(concatStringsSep("", strings), "thisisgreat"); ASSERT_EQ(dropEmptyInitThenConcatStringsSep("", strings), "thisisgreat");
} }
TEST(concatStringsSep, buildSingleString) { TEST(dropEmptyInitThenConcatStringsSep, buildSingleString) {
Strings strings; Strings strings;
strings.push_back("this"); strings.push_back("this");
ASSERT_EQ(concatStringsSep(",", strings), "this"); ASSERT_EQ(dropEmptyInitThenConcatStringsSep(",", strings), "this");
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------