blob: 98786d73469b81572613692d93f96e89de73ebad [file] [log] [blame]
//===- llvm/tools/gollvm/unittests/TestUtils/DiffUtils.cpp ----------------===//
//
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
//===----------------------------------------------------------------------===//
#include "DiffUtils.h"
#include <assert.h>
#include <iostream>
#include <sstream>
namespace goBackendUnitTests {
DECLARE_EXPECTED_OUTPUT(baseline, R"RAW_RESULT(
abc
def
xyz
)RAW_RESULT");
// macroLineAtStart returns TRUE if the line number reported by the
// compiler for __LINE__ in a macro instantiation points to the start
// of the DECLARE_EXPECTED_OUTPUT macro use, as opposed to the end.
//
// Fun fact: there is some disagreement among C++ compilers about what
// the line is. Some compilers decided that __LINE__ for the macro
// above is the starting line (16) and some decide that it's the
// ending line (20). Which one will matter for the machinery that does
// test remastering.
static bool macroLineAtStart() {
if (baseline.line == 16) {
return true;
} else if (baseline.line == 20) {
return false;
} else {
assert(false && "macroLineAtStart broken -- source edited?");
}
}
std::string trimsp(const std::string &s) {
size_t firstsp = s.find_first_not_of(" \n");
if (firstsp == std::string::npos)
return s;
size_t lastsp = s.find_last_not_of(" \n");
return s.substr(firstsp, (lastsp - firstsp + 1));
}
std::vector<std::string> tokenize(const std::string &s) {
std::vector<std::string> tokens;
const std::string del(" \t\n");
size_t np = s.find_first_not_of(del, 0);
size_t pos = s.find_first_of(del, np);
while (pos != std::string::npos || np != std::string::npos) {
tokens.push_back(s.substr(np, pos - np));
np = s.find_first_not_of(del, pos);
pos = s.find_first_of(del, np);
}
return tokens;
}
std::string vectostr(const std::vector<std::string> &tv) {
std::stringstream ss;
unsigned wc = 0;
for (auto s : tv)
ss << " " << wc++ << "[" << s << "]";
return ss.str();
}
bool difftokens(const std::string &expected, const std::string &result,
std::string &diffreason)
{
std::vector<std::string> expv = tokenize(expected);
std::vector<std::string> resv = tokenize(result);
unsigned mins = std::min(expv.size(), resv.size());
unsigned maxs = std::max(expv.size(), resv.size());
std::stringstream ss;
bool rval = true;
if (expv.size() != resv.size()) {
ss << "lengths differ (" << expv.size() << " vs " << resv.size()
<< ") extra " << (expv.size() > resv.size() ? "result" : "expected")
<< " tokens: ";
for (unsigned idx = 0; idx < maxs; ++idx) {
if (idx >= mins)
ss << (idx < expv.size() ? expv[idx] : resv[idx]) << " ";
}
ss << "\n";
rval = false;
}
for (unsigned idx = 0; idx < mins; ++idx) {
if (expv[idx] != resv[idx]) {
ss << "token vector diff at slot " << idx << " (expected '" << expv[idx]
<< "' result '" << resv[idx] << "')";
rval = false;
break;
}
}
if (! rval)
diffreason = ss.str();
return rval;
}
bool containstokens(const std::string &text, const std::string &pat)
{
std::vector<std::string> textToks = tokenize(text);
std::vector<std::string> patToks = tokenize(pat);
for (unsigned ti = 0; ti < textToks.size(); ++ti) {
bool failed = false;
for (unsigned tic = ti, pi = 0; pi < patToks.size(); ++pi, ++tic) {
if (tic >= textToks.size() || patToks[pi] != textToks[tic]) {
failed = true;
break;
}
}
if (failed)
continue;
return true;
}
return false;
}
unsigned countinstances(const std::string &text, const std::string &pat)
{
unsigned instances = 0;
std::vector<std::string> textToks = tokenize(text);
std::vector<std::string> patToks = tokenize(pat);
for (unsigned ti = 0; ti < textToks.size(); ++ti) {
bool fail = false;
for (unsigned tic = ti, pi = 0; pi < patToks.size(); ++pi, ++tic) {
if (tic >= textToks.size() || patToks[pi] != textToks[tic]) {
fail = true;
break;
}
}
if (!fail)
instances += 1;
}
return instances;
}
std::string dumpfilename(const char *tag, unsigned version) {
std::stringstream ss;
ss << "/tmp/" << tag << ".dump." << version << ".txt";
return ss.str();
}
void emitStringToDumpFile(const char *tag,
unsigned version,
const std::string &payload)
{
auto fn = dumpfilename(tag, version);
FILE *fp = fopen(fn.c_str(), "w");
if (fp) {
fprintf(fp, "%s\n", payload.c_str());
fclose(fp);
std::cerr << "emitted dump file " << fn << "\n";
}
}
void complainOnNequal(const std::string &reason,
const ExpectedDump &ed,
const std::string &actual)
{
bool emitDumpFilesOnDiff = false;
bool emitRemasterScript = false;
if (getenv("GOLLVM_UNITTESTS_BACKENDCORE_EMITDUMPFILES") != nullptr)
emitDumpFilesOnDiff = true;
if (getenv("GOLLVM_UNITTESTS_EMIT_REMASTER_SCRIPT") != nullptr) {
emitRemasterScript = true;
emitDumpFilesOnDiff = true;
}
std::cerr << reason << "\n";
const std::string &expected = ed.content;
std::cerr << "expected dump:\n" << expected << "\n";
std::cerr << "actual dump:\n" << actual << "\n";
unsigned mls = macroLineAtStart() ? 1 : 0;
if (emitDumpFilesOnDiff) {
static unsigned filecount;
static FILE *outfp; // script
emitStringToDumpFile("expected", filecount, expected);
emitStringToDumpFile("actual", filecount, actual);
if (emitRemasterScript) {
// HACK: no explicit close for this file. Assume that it will
// be closed when the unit test finishes running.
if (outfp == nullptr) {
outfp = fopen("/tmp/remaster-inputs.txt", "w");
fprintf(stderr, "... emitting remaster inputs to file '/tmp/remaster-inputs.txt'\n");
}
fprintf(outfp, "%d %s %d %s %s\n", mls, ed.file, ed.line,
dumpfilename("expected", filecount).c_str(),
dumpfilename("actual", filecount).c_str());
fflush(outfp);
}
filecount++;
}
}
} // end namespace