// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "WexTestClass.h" #include "../../inc/consoletaeftemplates.hpp" #include "alias.h" using namespace WEX::Common; using namespace WEX::Logging; using namespace WEX::TestExecution; class AliasTests { TEST_CLASS(AliasTests); TEST_CLASS_SETUP(ClassSetup) { return true; } TEST_METHOD_SETUP(MethodSetup) { // Don't let aliases spill across test functions. Alias::s_TestClearAliases(); return true; } DWORD _ReplacePercentWithCRLF(std::wstring& string) { DWORD linesExpected = 0; auto pos = string.find(L'%'); while (pos != std::wstring::npos) { PCWSTR newline = L"\r\n"; string = string.erase(pos, 1); string = string.insert(pos, newline); linesExpected++; // we expect one "line" per newline character returned. pos = string.find(L'%'); } return linesExpected; } void _RetrieveTargetExpectedPair(std::wstring& target, std::wstring& expected) { // Get test parameters String targetExpectedPair; VERIFY_SUCCEEDED(TestData::TryGetValue(L"targetExpectedPair", targetExpectedPair)); // Convert WEX strings into the wstrings int sepIndex = targetExpectedPair.Find(L'='); target = targetExpectedPair.Left(sepIndex); expected = targetExpectedPair.Mid(sepIndex + 1); } TEST_METHOD(TestMatchAndCopy) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:exeName", L"{test.exe}") TEST_METHOD_PROPERTY(L"Data:aliasName", L"{foo}") TEST_METHOD_PROPERTY(L"Data:originalString", L"{ foo one two three four five six seven eight nine ten eleven twelve }") TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" // Each of these is a human-generated test of macro before and after. L"bar=bar%," // The character % will be turned into an \r\n L"bar $1=bar one%," L"bar $2=bar two%," L"bar $3=bar three%," L"bar $4=bar four%," L"bar $5=bar five%," L"bar $6=bar six%," L"bar $7=bar seven%," L"bar $8=bar eight%," L"bar $9=bar nine%," L"bar $3 $1 $4 $1 $5 $9=bar three one four one five nine%," // assorted mixed order parameters with a repeat L"bar $*=bar one two three four five six seven eight nine ten eleven twelve%," L"longer=longer%," // replace with a target longer than the original alias L"redirect $1$goutput $2=redirect one>output two%," // doing these without spaces between some commands L"REDIRECT $1$GOUTPUT $2=REDIRECT one>OUTPUT two%," // also notice we're checking both upper and lowercase L"append $1$g$goutput $2=append one>>output two%," L"APPEND $1$G$GOUTPUT $2=APPEND one>>OUTPUT two%," L"redirect $1$linputfile.$2=redirect onefun one | test nine < two.txt%all$$the$$things one two three four five six seven eight nine ten eleven twelve%at>>once.log%" L"}") END_TEST_METHOD_PROPERTIES() // Get test parameters String exeName; VERIFY_SUCCEEDED(TestData::TryGetValue(L"exeName", exeName)); String aliasName; VERIFY_SUCCEEDED(TestData::TryGetValue(L"aliasName", aliasName)); String originalString; VERIFY_SUCCEEDED(TestData::TryGetValue(L"originalString", originalString)); // Prepare internal alias structures // Convert WEX strings into the wstrings we will use to feed into the Alias structures // and match to our expected values. std::wstring alias(aliasName); std::wstring exe(exeName); std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); DWORD linesExpected = _ReplacePercentWithCRLF(expected); std::wstring original(originalString); Alias::s_TestAddAlias(exe, alias, target); // Fill classic wchar_t[] buffer for interfacing with the MatchAndCopyAlias function const USHORT bufferSize = 160ui16; auto buffer = std::make_unique(bufferSize); wcscpy_s(buffer.get(), bufferSize, original.data()); const size_t cbBuffer = bufferSize * sizeof(wchar_t); size_t bufferUsed = 0; DWORD linesActual = 0; // Run the match and copy function. Alias::s_MatchAndCopyAliasLegacy(buffer.get(), wcslen(buffer.get()) * sizeof(wchar_t), buffer.get(), cbBuffer, bufferUsed, exe, linesActual); // Null terminate buffer for comparison buffer[bufferUsed / sizeof(wchar_t)] = L'\0'; Log::Comment(String().Format(L"Expected: '%s'", expected.data())); Log::Comment(String().Format(L"Actual : '%s'", buffer.get())); VERIFY_ARE_EQUAL(WEX::Common::String(expected.data()), WEX::Common::String(buffer.get())); VERIFY_ARE_EQUAL(linesExpected, linesActual); } TEST_METHOD(TestMatchAndCopyTrailingCRLF) { const auto pwszSource = L"SourceWithoutCRLF\r\n"; const size_t cbSource = wcslen(pwszSource) * sizeof(wchar_t); const size_t cchTarget = 60; auto rgwchTarget = std::make_unique(cchTarget); const size_t cbTarget = cchTarget * sizeof(wchar_t); wcscpy_s(rgwchTarget.get(), cchTarget, L"testtesttesttesttest"); auto rgwchTargetBefore = std::make_unique(cchTarget); wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); size_t cbTargetUsed = 0; DWORD dwLines = 0; // Register the wrong alias name before we try. std::wstring exe(L"exe.exe"); std::wstring sourceWithoutCRLF(L"SourceWithoutCRLF"); std::wstring target(L"someTarget"); Alias::s_TestAddAlias(exe, sourceWithoutCRLF, target); const auto targetExpected = target + L"\r\n"; const size_t cbTargetExpected = targetExpected.size() * sizeof(wchar_t); // +2 for \r\n that will be added on replace. Alias::s_MatchAndCopyAliasLegacy(pwszSource, cbSource, rgwchTarget.get(), cbTarget, cbTargetUsed, exe, dwLines); // Terminate target buffer with \0 for comparison rgwchTarget[cbTargetUsed] = L'\0'; VERIFY_ARE_EQUAL(cbTargetExpected, cbTargetUsed, L"Target bytes should be filled with target size."); VERIFY_ARE_EQUAL(String(targetExpected.data()), String(rgwchTarget.get(), gsl::narrow(cbTargetUsed / sizeof(wchar_t))), L"Target string should be filled with target data."); VERIFY_ARE_EQUAL(1u, dwLines, L"Line count should be 1."); } TEST_METHOD(TestMatchAndCopyInvalidExeName) { const auto pwszSource = L"Source"; const size_t cbSource = wcslen(pwszSource) * sizeof(wchar_t); const size_t cchTarget = 12; auto rgwchTarget = std::make_unique(cchTarget); wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); auto rgwchTargetBefore = std::make_unique(cchTarget); wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); const size_t cbTarget = cchTarget * sizeof(wchar_t); size_t cbTargetUsed = cbTarget; DWORD dwLines = 0; auto const dwLinesBefore = dwLines; std::wstring exeName; Alias::s_MatchAndCopyAliasLegacy(pwszSource, cbSource, rgwchTarget.get(), cbTarget, cbTargetUsed, exeName, dwLines); VERIFY_ARE_EQUAL(cbTarget, cbTargetUsed, L"Byte count shouldn't have changed with failure."); VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count shouldn't have changed with failure."); VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string shouldn't have changed with failure."); } TEST_METHOD(TestMatchAndCopyExeNotFound) { const auto pwszSource = L"Source"; const size_t cbSource = wcslen(pwszSource) * sizeof(wchar_t); const size_t cchTarget = 12; auto rgwchTarget = std::make_unique(cchTarget); const size_t cbTarget = cchTarget * sizeof(wchar_t); wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); auto rgwchTargetBefore = std::make_unique(cchTarget); wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); size_t cbTargetUsed = 0; auto const cbTargetUsedBefore = cbTargetUsed; std::wstring exeName(L"exe.exe"); DWORD dwLines = 0; auto const dwLinesBefore = dwLines; Alias::s_MatchAndCopyAliasLegacy(pwszSource, cbSource, rgwchTarget.get(), cbTarget, cbTargetUsed, exeName, // we didn't pre-set-up the exe name dwLines); VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"No bytes should have been written."); VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string should be unmodified."); VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count should pass through."); } TEST_METHOD(TestMatchAndCopyAliasNotFound) { const auto pwszSource = L"Source"; const size_t cbSource = wcslen(pwszSource) * sizeof(wchar_t); const size_t cchTarget = 12; auto rgwchTarget = std::make_unique(cchTarget); const size_t cbTarget = cchTarget * sizeof(wchar_t); wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); auto rgwchTargetBefore = std::make_unique(cchTarget); wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); size_t cbTargetUsed = 0; auto const cbTargetUsedBefore = cbTargetUsed; DWORD dwLines = 0; auto const dwLinesBefore = dwLines; // Register the wrong alias name before we try. std::wstring exe(L"exe.exe"); std::wstring badSource(L"wrongSource"); std::wstring target(L"someTarget"); Alias::s_TestAddAlias(exe, badSource, target); Alias::s_MatchAndCopyAliasLegacy(pwszSource, cbSource, rgwchTarget.get(), cbTarget, cbTargetUsed, exe, dwLines); VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"No bytes should be used if nothing was found."); VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string should be unmodified."); VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count should pass through."); } TEST_METHOD(TestMatchAndCopyTargetTooSmall) { const auto pwszSource = L"Source"; const size_t cbSource = wcslen(pwszSource) * sizeof(wchar_t); const size_t cchTarget = 12; auto rgwchTarget = std::make_unique(cchTarget); wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); auto rgwchTargetBefore = std::make_unique(cchTarget); wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); size_t cbTargetUsed = 0; auto const cbTargetUsedBefore = cbTargetUsed; DWORD dwLines = 0; auto const dwLinesBefore = dwLines; // Register the correct alias name before we try. std::wstring exe(L"exe.exe"); std::wstring source(pwszSource); std::wstring target(L"someTarget"); Alias::s_TestAddAlias(exe, source, target); Alias::s_MatchAndCopyAliasLegacy(pwszSource, cbSource, rgwchTarget.get(), 1, // Make the target size too small cbTargetUsed, exe, dwLines); VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"Byte count shouldn't have changed with failure."); VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count shouldn't have changed with failure."); VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string shouldn't have changed with failure."); } TEST_METHOD(TestMatchAndCopyLeadingSpaces) { const auto pwszSource = L" Source"; const size_t cbSource = wcslen(pwszSource) * sizeof(wchar_t); const size_t cchTarget = 12; auto rgwchTarget = std::make_unique(cchTarget); const size_t cbTarget = cchTarget * sizeof(wchar_t); wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); auto rgwchTargetBefore = std::make_unique(cchTarget); wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); size_t cbTargetUsed = 0; auto const cbTargetUsedExpected = cbTarget; DWORD dwLines = 0; auto const dwLinesExpected = dwLines + 1; // Register the correct alias name before we try. std::wstring exe(L"exe.exe"); std::wstring source(L"Source"); std::wstring target(L"someTarget"); Alias::s_TestAddAlias(exe, source, target); std::wstring targetExpected = target + L"\r\n"; // We should be able to match through the leading spaces. They should be stripped. Alias::s_MatchAndCopyAliasLegacy(pwszSource, cbSource, rgwchTarget.get(), cbTarget, cbTargetUsed, exe, dwLines); VERIFY_ARE_EQUAL(cbTargetUsedExpected, cbTargetUsed, L"No target bytes should be used."); VERIFY_ARE_EQUAL(String(targetExpected.data(), gsl::narrow(targetExpected.size())), String(rgwchTarget.get(), cchTarget), L"Target string should match expected."); VERIFY_ARE_EQUAL(dwLinesExpected, dwLines, L"Line count be updated to 1."); } TEST_METHOD(TrimTrailing) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" L"bar%=bar," // The character % will be turned into an \r\n L"bar=bar" L"}") END_TEST_METHOD_PROPERTIES() std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); // Substitute %s from metadata into \r\n (since metadata can't hold \r\n) _ReplacePercentWithCRLF(target); _ReplacePercentWithCRLF(expected); Alias::s_TrimTrailingCrLf(target); VERIFY_ARE_EQUAL(String(expected.data()), String(target.data())); } TEST_METHOD(Tokenize) { std::wstring tokenStr(L"one two three"); std::deque tokensExpected; tokensExpected.emplace_back(L"one"); tokensExpected.emplace_back(L"two"); tokensExpected.emplace_back(L"three"); auto tokensActual = Alias::s_Tokenize(tokenStr); VERIFY_ARE_EQUAL(tokensExpected.size(), tokensActual.size()); for (size_t i = 0; i < tokensExpected.size(); i++) { VERIFY_ARE_EQUAL(String(tokensExpected[i].data()), String(tokensActual[i].data())); } } TEST_METHOD(TokenizeNothing) { std::wstring tokenStr(L"alias"); std::deque tokensExpected; tokensExpected.emplace_back(tokenStr); auto tokensActual = Alias::s_Tokenize(tokenStr); VERIFY_ARE_EQUAL(tokensExpected.size(), tokensActual.size()); for (size_t i = 0; i < tokensExpected.size(); i++) { VERIFY_ARE_EQUAL(String(tokensExpected[i].data()), String(tokensActual[i].data())); } } TEST_METHOD(GetArgString) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" L"alias arg1 arg2 arg3=arg1 arg2 arg3," L"aliasOnly=" L"}") END_TEST_METHOD_PROPERTIES() std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); std::wstring actual = Alias::s_GetArgString(target); VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); } TEST_METHOD(NumberedArgMacro) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" L"1=one," L"2=two," L"3=three," L"4=four," L"5=five," L"6=six," L"7=seven," L"8=eight," L"9=nine," L"A=," L"0=," L"}") END_TEST_METHOD_PROPERTIES() std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); std::deque tokens; tokens.emplace_back(L"alias"); tokens.emplace_back(L"one"); tokens.emplace_back(L"two"); tokens.emplace_back(L"three"); tokens.emplace_back(L"four"); tokens.emplace_back(L"five"); tokens.emplace_back(L"six"); tokens.emplace_back(L"seven"); tokens.emplace_back(L"eight"); tokens.emplace_back(L"nine"); tokens.emplace_back(L"ten"); // if we expect non-empty results, then we should get a bool back saying it was processed const bool returnExpected = !expected.empty(); std::wstring actual; const bool returnActual = Alias::s_TryReplaceNumberedArgMacro(target[0], actual, tokens); VERIFY_ARE_EQUAL(returnExpected, returnActual); VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); } TEST_METHOD(WildcardArgMacro) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" L"*=one two three," L"A=," L"0=," L"}") END_TEST_METHOD_PROPERTIES() std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); std::wstring fullArgString(L"one two three"); // if we expect non-empty results, then we should get a bool back saying it was processed const bool returnExpected = !expected.empty(); std::wstring actual; const bool returnActual = Alias::s_TryReplaceWildcardArgMacro(target[0], actual, fullArgString); VERIFY_ARE_EQUAL(returnExpected, returnActual); VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); } TEST_METHOD(InputRedirMacro) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" L"L=<," L"l=<," L"A=," L"a=," L"0=," L"}") END_TEST_METHOD_PROPERTIES() std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); // if we expect non-empty results, then we should get a bool back saying it was processed const bool returnExpected = !expected.empty(); std::wstring actual; const bool returnActual = Alias::s_TryReplaceInputRedirMacro(target[0], actual); VERIFY_ARE_EQUAL(returnExpected, returnActual); VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); } TEST_METHOD(OutputRedirMacro) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" L"G=>," L"g=>," L"A=," L"a=," L"0=," L"}") END_TEST_METHOD_PROPERTIES() std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); // if we expect non-empty results, then we should get a bool back saying it was processed const bool returnExpected = !expected.empty(); std::wstring actual; const bool returnActual = Alias::s_TryReplaceOutputRedirMacro(target[0], actual); VERIFY_ARE_EQUAL(returnExpected, returnActual); VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); } TEST_METHOD(PipeRedirMacro) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" L"B=|," L"b=|," L"A=," L"a=," L"0=," L"}") END_TEST_METHOD_PROPERTIES() std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); // if we expect non-empty results, then we should get a bool back saying it was processed const bool returnExpected = !expected.empty(); std::wstring actual; const bool returnActual = Alias::s_TryReplacePipeRedirMacro(target[0], actual); VERIFY_ARE_EQUAL(returnExpected, returnActual); VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); } TEST_METHOD(NextCommandMacro) { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" L"T=%," L"t=%," L"A=," L"a=," L"0=," L"}") END_TEST_METHOD_PROPERTIES() std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); _ReplacePercentWithCRLF(expected); // if we expect non-empty results, then we should get a bool back saying it was processed const bool returnExpected = !expected.empty(); std::wstring actual; size_t lineCountActual = 0; const auto lineCountExpected = lineCountActual + (returnExpected ? 1 : 0); const bool returnActual = Alias::s_TryReplaceNextCommandMacro(target[0], actual, lineCountActual); VERIFY_ARE_EQUAL(returnExpected, returnActual); VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); VERIFY_ARE_EQUAL(lineCountExpected, lineCountActual); } TEST_METHOD(AppendCrLf) { std::wstring actual; size_t lineCountActual = 0; const std::wstring expected(L"\r\n"); const auto lineCountExpected = lineCountActual + 1; Alias::s_AppendCrLf(actual, lineCountActual); VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); VERIFY_ARE_EQUAL(lineCountExpected, lineCountActual); } };