/* SPDX-FileCopyrightText: 2024 Jonathan Poelen SPDX-License-Identifier: LGPL-2.0-or-later */ #include "scripttester_test.h" #include "../src/scripttester/scripttester_p.h" #include "katedocument.h" #include "moc_scripttester_test.cpp" #include #include #include #include #include #include using namespace Qt::Literals::StringLiterals; namespace { struct CompareData { QString program; QString expectedOutput; }; } static void compareOutput(const QString &suffixFile, QJSEngine &engine, KTextEditor::ScriptTester &scriptTester, QBuffer &buffer, CompareData d) { buffer.open(QBuffer::WriteOnly); auto toString = [](const QJSValue &value) -> QString { if (!value.isError()) { return value.toString(); } return value.toString() + u": "_s + value.property(u"stack"_s).toString(); }; scriptTester.resetCounters(); /* * execute */ QJSValue result = engine.evaluate(d.program, u"myfile"_s, 0); QCOMPARE(toString(result), u"function() { [native code] }"_s); QJSValue globalObject = engine.globalObject(); QJSValue functions = engine.newQObject(&scriptTester); result = result.callWithInstance(functions, {globalObject}); QCOMPARE(toString(result), u"undefined"_s); /* * write counters and flush */ scriptTester.writeSummary(); scriptTester.stream() << '\n'; scriptTester.stream().flush(); /* * extract output */ const QString outputResult = QStringDecoder(QStringDecoder::Utf8)(buffer.data()); /* * clear buffer */ buffer.close(); buffer.buffer().clear(); /* * comparison */ const bool equalResults = outputResult == d.expectedOutput; if (equalResults) { return; } /* * write buffers to files */ const QString tempdir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); const QString resultPath = tempdir + u"/scripttester_"_s + suffixFile + u"_result.txt"_s; const QString exptedPath = tempdir + u"/scripttester_"_s + suffixFile + u"_expected.txt"_s; QFile outFile; // result outFile.setFileName(resultPath); QVERIFY(outFile.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream(&outFile) << outputResult; outFile.close(); // expected outFile.setFileName(exptedPath); QVERIFY(outFile.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream(&outFile) << d.expectedOutput; outFile.close(); /* * elaborate diff output, if possible */ const QString diffExecutable = QStandardPaths::findExecutable(u"diff"_s); if (!diffExecutable.isEmpty()) { QProcess proc; proc.setProcessChannelMode(QProcess::ForwardedChannels); proc.start(diffExecutable, {u"-u"_s, exptedPath, resultPath}); QVERIFY(proc.waitForFinished()); QCOMPARE(proc.exitCode(), 0); } /* * else: trivial output of mismatching characters, e.g. for windows testing without diff */ else { qDebug() << "Trivial differences output as the 'diff' executable is not in the PATH"; auto expectedLines = QStringView(d.expectedOutput).split(u'\n'); auto outputLines = QStringView(outputResult).split(u'\n'); for (qsizetype i = 0; i < expectedLines.size() || i < outputLines.size(); ++i) { QStringView expectedLine = i < expectedLines.size() ? expectedLines[i] : QStringView(); QStringView outputLine = i < outputLines.size() ? outputLines[i] : QStringView(); QCOMPARE(expectedLine, outputLine); } QCOMPARE(d.expectedOutput.size(), outputResult.size()); } /* * if we arrive here, all lost! */ QVERIFY(equalResults); } QTEST_MAIN(ScriptTesterTest) #define testNewRow() (QTest::newRow(QString("line %1").arg(__LINE__).toAscii().data())) ScriptTesterTest::ScriptTesterTest() : QObject() { QStandardPaths::setTestModeEnabled(true); } ScriptTesterTest::~ScriptTesterTest() { } void ScriptTesterTest::testDebug() { } void ScriptTesterTest::testPrintExpression() { using ScriptTester = KTextEditor::ScriptTester; using TextFormat = ScriptTester::DocumentTextFormat; using TestFormatOption = ScriptTester::TestFormatOption; // using PatternType = ScriptTester::PatternType; using DebugOption = ScriptTester::DebugOption; KTextEditor::DocumentPrivate doc(true, false); KTextEditor::ViewPrivate view(&doc, nullptr); QJSEngine engine; QBuffer buffer; ScriptTester scriptTester(&buffer, ScriptTester::Format{ .debugOptions = DebugOption::WriteLocation | DebugOption::WriteFunction, .testFormatOptions = TestFormatOption::None, .documentTextFormat = TextFormat::ReplaceNewLineAndTabWithLiteral, .documentTextFormatWithBlockSelection = TextFormat::ReplaceNewLineAndTabWithPlaceholder, .textReplacement = ScriptTester::Format::TextReplacement{ .newLine = u'↵', .tab1 = u'—', .tab2 = u'⇥', }, .fallbackPlaceholders = ScriptTester::Placeholders{ .cursor = u'|', .selectionStart = u'[', .selectionEnd = u']', .secondaryCursor = u'┆', .secondarySelectionStart = u'❲', .secondarySelectionEnd = u'❳', .virtualText = u'·', }, .colors = { .reset = u""_s, .success = u""_s, .error = u""_s, .carret = u""_s, .debugMarker = u""_s, .debugMsg = u""_s, .testName = u""_s, .program = u""_s, .fileName = u""_s, .lineNumber = u""_s, .blockSelectionInfo = u""_s, .labelInfo = u""_s, .cursor = u""_s, .selection = u""_s, .secondaryCursor = u""_s, .secondarySelection = u""_s, .blockSelection = u""_s, .inSelection = u""_s, .virtualText = u""_s, .result = u""_s, .resultReplacement = u""_s, }, }, ScriptTester::Paths{}, ScriptTester::TestExecutionConfig{}, ScriptTester::DiffCommand{}, ScriptTester::Placeholders{ .cursor = u'|', .selectionStart = u'[', .selectionEnd = u']', .secondaryCursor = u'\0', .secondarySelectionStart = u'\0', .secondarySelectionEnd = u'\0', .virtualText = u'\0', }, &engine, &doc, &view); // add debug() function QJSValue globalObject = engine.globalObject(); QJSValue functions = engine.newQObject(&scriptTester); globalObject.setProperty(u"debug"_s, functions.property(u"debug"_s)); auto makeProgram = [](QString program) -> QString { return u"(function(env, argv){" u"const TestFramework = this.loadModule('" JS_SCRIPTTESTER_DATA_DIR u"testframework.js');" u"TestFramework.init(this, env, 2);" u"var {lt, cmd, print, config, testCase, withInput} = TestFramework;" u""_s + program + u"})"_s; }; compareOutput(u"testCase"_s, engine, scriptTester, buffer, CompareData{ .program = makeProgram(uR"( function foo() { return true; } config({blockSelection: 0}) testCase('MyTest', () => { cmd(foo, 'abc\ndef', 'abc\ndef|') // no error cmd(foo, 'abc\ndef', 'abc\ndef|', { expected: 1 }) cmd(foo, 'abc', 'abc\ndef|', { expected: {a:42} }) config({virtualText: '@', blockSelection: 1}) cmd(foo, 'abcxxxxxxxxx|[\ndaa aaa\ndaaaa]aaaaaef', 'abc@@@|\nabc\ndef') ; }); )"_s), .expectedOutput = uR"(myfile:5: MyTest: Result differs cmd `foo() === {expectedResult}` [blockSelection=0]: input: abc\ndef| output: abc\ndef| --------- result: true expected: 1 ^~~ myfile:6: MyTest: Output and Result differs cmd `foo() === {expectedResult}` [blockSelection=0]: input: abc| output: abc| expected: abc\ndef| ^~~ --------- result: true expected: {a: 42} ^~~ myfile:8: MyTest: Output differs cmd `foo()` [blockSelection=1]: input: abcxx[xxxxxxx|]daa a[aa@@@@@]daaaa[aaaaaef] output: abcxx[xxxxxxx|]^~~ daa a[aa@@@@@]daaaa[aaaaaef] expected: abc@@@|^~~ abcdef Success: 1 Failure: 3 )"_s, }); compareOutput(u"withInput"_s, engine, scriptTester, buffer, CompareData{ .program = makeProgram(uR"( function foo() { return true; } config({blockSelection: 0}) withInput('MyTest2', 'abc|', () => { print('print'); cmd(foo, TestFramework.EXPECTED_OUTPUT_AS_INPUT); // no error cmd(foo, 'abc\ndef|'); cmd(foo, 'abc\ndef|', { expected: {a:42} }); lt(foo, 1); }); )"_s), .expectedOutput = uR"(myfile:4: PRINT: print myfile:6: MyTest2: Output differs cmd `foo()` [blockSelection=0]: input: abc| output: abc| expected: abc\ndef| ^~~ myfile:7: MyTest2: Output and Result differs cmd `foo() === {expectedResult}` [blockSelection=0]: input: abc| output: abc| expected: abc\ndef| ^~~ --------- result: true expected: {a: 42} ^~~ myfile:8: MyTest2: Result differs test `foo() < {expected}` [blockSelection=0]: result: true expected: 1 ^~~ Success: 1 Failure: 3 )"_s, }); }