| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file LICENSE.rst or https://cmake.org/licensing for details. */ |
| #include <iostream> |
| #include <string> |
| |
| #include <cm/memory> |
| |
| #include "cmGeneratorExpression.h" |
| #include "cmGlobalGenerator.h" |
| #include "cmLocalGenerator.h" |
| #include "cmMakefile.h" |
| #include "cmState.h" |
| #include "cmStateDirectory.h" |
| #include "cmStateSnapshot.h" |
| #include "cmake.h" |
| |
| namespace { |
| struct GenExFixture |
| { |
| cmake CMake{ cmState::Role::Project }; |
| std::unique_ptr<cmGlobalGenerator> GG; |
| std::unique_ptr<cmMakefile> MF; |
| std::unique_ptr<cmLocalGenerator> LG; |
| |
| GenExFixture() |
| { |
| this->GG = cm::make_unique<cmGlobalGenerator>(&this->CMake); |
| cmStateSnapshot snapshot = this->CMake.GetCurrentSnapshot(); |
| snapshot.GetDirectory().SetCurrentBinary("."); |
| snapshot.GetDirectory().SetCurrentSource("."); |
| this->MF = cm::make_unique<cmMakefile>(this->GG.get(), snapshot); |
| this->LG = this->GG->CreateLocalGenerator(this->MF.get()); |
| } |
| |
| std::string Eval(std::string const& expr) |
| { |
| return cmGeneratorExpression::Evaluate(expr, this->LG.get(), "Debug"); |
| } |
| }; |
| } |
| |
| static bool testApplyBasic() |
| { |
| GenExFixture fx; |
| return fx.Eval("$<LIST:TRANSFORM,net;audio,APPLY,$<UPPER_CASE:$<_0>>>") == |
| "NET;AUDIO"; |
| } |
| |
| static bool testApplyConstantBody() |
| { |
| // The body need not reference the operand at all. |
| GenExFixture fx; |
| return fx.Eval("$<LIST:TRANSFORM,a;b;c,APPLY,X>") == "X;X;X"; |
| } |
| |
| static bool testApplyOperandUsedTwice() |
| { |
| // The operand may be substituted more than once in a single body. |
| GenExFixture fx; |
| return fx.Eval("$<LIST:TRANSFORM,a;b,APPLY,$<_0>$<_0>>") == "aa;bb"; |
| } |
| |
| static bool testApplyFlatMapExpands() |
| { |
| GenExFixture fx; |
| // A list-valued body expands: a -> "x;y", b -> "z" => 3 items. |
| std::string out = |
| fx.Eval("$<LIST:TRANSFORM,a;b,APPLY,$<IF:$<STREQUAL:$<_0>,a>,x;y,z>>"); |
| std::string len = fx.Eval("$<LIST:LENGTH,$<LIST:TRANSFORM,a;b,APPLY," |
| "$<IF:$<STREQUAL:$<_0>,a>,x;y,z>>>"); |
| return out == "x;y;z" && len == "3"; |
| } |
| |
| static bool testApplySelector() |
| { |
| GenExFixture fx; |
| return fx.Eval( |
| "$<LIST:TRANSFORM,a;b;c;d,APPLY,$<UPPER_CASE:$<_0>>,AT,0,2>") == |
| "A;b;C;d"; |
| } |
| |
| static bool testApplyNestedShadowing() |
| { |
| GenExFixture fx; |
| return fx.Eval( |
| "$<LIST:TRANSFORM,a;b,APPLY," |
| "$<LIST:TRANSFORM,$<_0>1;$<_0>2,APPLY,$<UPPER_CASE:$<_0>>>>") == |
| "A1;A2;B1;B2"; |
| } |
| |
| static bool testApplyEmptyList() |
| { |
| GenExFixture fx; |
| // An empty list produces an empty string without error (matches canned |
| // TRANSFORM behavior). |
| std::string got = fx.Eval("$<LIST:TRANSFORM,,APPLY,x$<_0>y>"); |
| if (!got.empty()) { |
| std::cerr << "testApplyEmptyList: expected empty string, got '" << got |
| << "'\n"; |
| return false; |
| } |
| return true; |
| } |
| |
| static bool testApplyEmptyElement() |
| { |
| GenExFixture fx; |
| // Every element is selected, including the leading empty one, so the body |
| // wraps each: "" -> "xy", "b" -> "xby". |
| return fx.Eval("$<LIST:TRANSFORM,;b,APPLY,x$<_0>y>") == "xy;xby"; |
| } |
| |
| static bool testApplyEmptyPassthrough() |
| { |
| GenExFixture fx; |
| // The list ";b" has two elements: "" (index 0, unselected) and "b" |
| // (index 1, selected). The unselected empty element must pass through |
| // verbatim, not be silently dropped. |
| std::string got = fx.Eval("$<LIST:TRANSFORM,;b,APPLY,X$<_0>Y,AT,1>"); |
| if (got != ";XbY") { |
| std::cerr << "testApplyEmptyPassthrough: expected ';XbY', got '" << got |
| << "'\n"; |
| return false; |
| } |
| return true; |
| } |
| |
| static bool testApplyEmptyResult() |
| { |
| GenExFixture fx; |
| // Body yields "" for "a" and "z" for "b". The empty element produced by |
| // the body must not be dropped. |
| std::string got = |
| fx.Eval("$<LIST:TRANSFORM,a;b,APPLY,$<IF:$<STREQUAL:$<_0>,a>,,z>>"); |
| if (got != ";z") { |
| std::cerr << "testApplyEmptyResult: expected ';z', got '" << got << "'\n"; |
| return false; |
| } |
| return true; |
| } |
| int testGenExTransformApply(int /*argc*/, char* /*argv*/[]) |
| { |
| if (!testApplyBasic()) { |
| return 1; |
| } |
| if (!testApplyConstantBody()) { |
| return 1; |
| } |
| if (!testApplyOperandUsedTwice()) { |
| return 1; |
| } |
| if (!testApplyFlatMapExpands()) { |
| return 1; |
| } |
| if (!testApplySelector()) { |
| return 1; |
| } |
| if (!testApplyNestedShadowing()) { |
| return 1; |
| } |
| if (!testApplyEmptyList()) { |
| return 1; |
| } |
| if (!testApplyEmptyElement()) { |
| return 1; |
| } |
| if (!testApplyEmptyPassthrough()) { |
| return 1; |
| } |
| if (!testApplyEmptyResult()) { |
| return 1; |
| } |
| return 0; |
| } |