File indexing completed on 2024-04-21 05:42:43
0001 /* 0002 SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de 0003 SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "diff.h" 0007 #include "gnudiff_diff.h" 0008 #include "LineRef.h" 0009 #include "options.h" 0010 #include "progress.h" 0011 #include "SourceData.h" 0012 0013 #include <iostream> 0014 #include <memory> 0015 #include <stdio.h> 0016 0017 #include <QDirIterator> 0018 #include <QRegularExpression> 0019 #include <QTextCodec> 0020 #include <QTextStream> 0021 0022 0023 #define i18n(s) s 0024 0025 bool verbose = false; 0026 std::unique_ptr<Options> gOptions = nullptr; 0027 ManualDiffHelpList m_manualDiffHelpList; 0028 0029 void printDiffList(const QString caption, const DiffList &diffList) 0030 { 0031 QTextStream out(stdout); 0032 DiffList::const_iterator i; 0033 0034 out << "Printing difflist " << caption << ":" << endl; 0035 out << " nofEquals, diff1, diff2" << endl; 0036 0037 for(i = diffList.begin(); i != diffList.end(); i++) 0038 { 0039 out << " " << i->numberOfEquals() << "," << i->diff1() << "," << i->diff2() << endl; 0040 } 0041 } 0042 0043 void printDiff3List(const Diff3LineList &diff3LineList, 0044 const SourceData &sd1, 0045 const SourceData &sd2, 0046 const SourceData &sd3, 0047 bool forceVerbosity=false) 0048 { 0049 const int columnsize = 30; 0050 const int linenumsize = 6; 0051 Diff3LineList::const_iterator i; 0052 for ( i=diff3LineList.begin(); i!=diff3LineList.end(); ++i ) 0053 { 0054 QTextStream out(stdout); 0055 QString lineAText; 0056 QString lineBText; 0057 QString lineCText; 0058 0059 const Diff3Line& d3l = *i; 0060 0061 if(d3l.getLineA().isValid()) 0062 { 0063 const LineData *pLineData = &sd1.getLineDataForDiff()->at(d3l.getLineA()); 0064 lineAText = pLineData->getLine(); 0065 lineAText.replace(QString("\r"), QString("\\r")); 0066 lineAText.replace(QString("\n"), QString("\\n")); 0067 lineAText = QString("%1 %2").arg(d3l.getLineA(), linenumsize).arg(lineAText.left(columnsize - linenumsize - 1)); 0068 } 0069 0070 if(d3l.getLineB().isValid()) 0071 { 0072 const LineData *pLineData = &sd2.getLineDataForDiff()->at(d3l.getLineB()); 0073 lineBText = pLineData->getLine(); 0074 lineBText.replace(QString("\r"), QString("\\r")); 0075 lineBText.replace(QString("\n"), QString("\\n")); 0076 lineBText = QString("%1 %2").arg(d3l.getLineB(), linenumsize).arg(lineBText.left(columnsize - linenumsize - 1)); 0077 } 0078 0079 if(d3l.getLineC().isValid()) 0080 { 0081 const LineData *pLineData = &sd3.getLineDataForDiff()->at(d3l.getLineC()); 0082 lineCText = pLineData->getLine(); 0083 lineCText.replace(QString("\r"), QString("\\r")); 0084 lineCText.replace(QString("\n"), QString("\\n")); 0085 lineCText = QString("%1 %2").arg(d3l.getLineC(), linenumsize).arg(lineCText.left(columnsize - linenumsize - 1)); 0086 } 0087 0088 out << QString("%1 %2 %3").arg(lineAText, -columnsize) 0089 .arg(lineBText, -columnsize) 0090 .arg(lineCText, -columnsize); 0091 if(verbose || forceVerbosity) 0092 { 0093 out << " " << d3l.isEqualAB() << " " << d3l.isEqualBC() << " " << d3l.isEqualAC(); 0094 } 0095 0096 out << endl; 0097 } 0098 } 0099 0100 void printDiff3List(QString caption, 0101 const Diff3LineList &diff3LineList, 0102 const SourceData &sd1, 0103 const SourceData &sd2, 0104 const SourceData &sd3, 0105 bool forceVerbosity=false) 0106 { 0107 QTextStream out(stdout); 0108 out << "Printing diff3list " << caption << ":" << endl; 0109 printDiff3List(diff3LineList, sd1, sd2, sd3, forceVerbosity); 0110 } 0111 0112 void determineFileAlignment(SourceData &m_sd1, SourceData &m_sd2, SourceData &m_sd3, Diff3LineList &m_diff3LineList) 0113 { 0114 DiffList m_diffList12; 0115 DiffList m_diffList23; 0116 DiffList m_diffList13; 0117 0118 m_diff3LineList.clear(); 0119 0120 // Run the diff. 0121 if ( m_sd3.isEmpty() ) 0122 { 0123 m_manualDiffHelpList.runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12, e_SrcSelector::A, e_SrcSelector::B); 0124 m_diff3LineList.calcDiff3LineListUsingAB( &m_diffList12); 0125 m_diff3LineList.fineDiff(e_SrcSelector::A, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay(), IgnoreFlag::none); 0126 } 0127 else 0128 { 0129 m_manualDiffHelpList.runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12, e_SrcSelector::A, e_SrcSelector::B); 0130 m_manualDiffHelpList.runDiff(m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList23, e_SrcSelector::B, e_SrcSelector::C); 0131 m_manualDiffHelpList.runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList13, e_SrcSelector::A, e_SrcSelector::C); 0132 0133 if (verbose) 0134 { 0135 printDiffList("m_diffList12", m_diffList12); 0136 printDiffList("m_diffList23", m_diffList23); 0137 printDiffList("m_diffList13", m_diffList13); 0138 } 0139 0140 m_diff3LineList.calcDiff3LineListUsingAB( &m_diffList12); 0141 if (verbose) printDiff3List("after calcDiff3LineListUsingAB", m_diff3LineList, m_sd1, m_sd2, m_sd3); 0142 0143 m_diff3LineList.calcDiff3LineListUsingAC( &m_diffList13); 0144 if (verbose) printDiff3List("after calcDiff3LineListUsingAC", m_diff3LineList, m_sd1, m_sd2, m_sd3); 0145 0146 m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList ); 0147 m_diff3LineList.calcDiff3LineListTrim(m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList ); 0148 if (verbose) printDiff3List("after 1st calcDiff3LineListTrim", m_diff3LineList, m_sd1, m_sd2, m_sd3); 0149 0150 if(gOptions->m_bDiff3AlignBC) 0151 { 0152 m_diff3LineList.calcDiff3LineListUsingBC( &m_diffList23); 0153 if (verbose) printDiff3List("after calcDiff3LineListUsingBC", m_diff3LineList, m_sd1, m_sd2, m_sd3); 0154 m_diff3LineList.correctManualDiffAlignment( &m_manualDiffHelpList ); 0155 m_diff3LineList.calcDiff3LineListTrim(m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList ); 0156 if (verbose) printDiff3List("after 2nd calcDiff3LineListTrim", m_diff3LineList, m_sd1, m_sd2, m_sd3); 0157 } 0158 0159 m_diff3LineList.fineDiff(e_SrcSelector::A, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay(), IgnoreFlag::none ); 0160 m_diff3LineList.fineDiff(e_SrcSelector::B, m_sd2.getLineDataForDisplay(), m_sd3.getLineDataForDisplay(), IgnoreFlag::none ); 0161 m_diff3LineList.fineDiff(e_SrcSelector::C, m_sd3.getLineDataForDisplay(), m_sd1.getLineDataForDisplay(), IgnoreFlag::none ); 0162 } 0163 m_diff3LineList.calcWhiteDiff3Lines( m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), false); 0164 } 0165 0166 QString getLineFromSourceData(const SourceData &sd, int line) 0167 { 0168 const LineData *pLineData = &sd.getLineDataForDiff()->at(line); 0169 QString lineText = pLineData->getLine(); 0170 lineText.replace(QString("\r"), QString("\\r")); 0171 lineText.replace(QString("\n"), QString("\\n")); 0172 return lineText; 0173 } 0174 0175 0176 void loadExpectedAlignmentFile(QString expectedResultFileName, Diff3LineList &expectedDiff3LineList) 0177 { 0178 Diff3Line d3l; 0179 0180 expectedDiff3LineList.clear(); 0181 0182 QFile file(expectedResultFileName); 0183 QString line; 0184 if ( file.open(QIODevice::ReadOnly) ) 0185 { 0186 QTextStream t( &file ); 0187 while ( !t.atEnd() ) 0188 { 0189 QStringList lst = t.readLine().split(QRegularExpression("\\s+")); 0190 d3l.setLineA(lst.at(0).toInt()); 0191 d3l.setLineB(lst.at(1).toInt()); 0192 d3l.setLineC(lst.at(2).toInt()); 0193 0194 expectedDiff3LineList.push_back( d3l ); 0195 } 0196 file.close(); 0197 } 0198 } 0199 0200 void writeActualAlignmentFile(QString actualResultFileName, const Diff3LineList &actualDiff3LineList) 0201 { 0202 Diff3LineList::const_iterator p_d3l; 0203 0204 QFile file(actualResultFileName); 0205 if ( file.open(QIODevice::WriteOnly) ) 0206 { 0207 { 0208 QTextStream t( &file ); 0209 0210 for(p_d3l = actualDiff3LineList.begin(); p_d3l != actualDiff3LineList.end(); p_d3l++) 0211 { 0212 t << p_d3l->getLineA() << " " << p_d3l->getLineB() << " " << p_d3l->getLineC() << endl; 0213 } 0214 } 0215 file.close(); 0216 } 0217 } 0218 0219 bool dataIsConsistent(LineRef line1, QString &line1Text, LineRef line2, QString &line2Text, bool equal) 0220 { 0221 bool consistent = false; 0222 0223 if(!line1.isValid() || line2.isValid()) 0224 { 0225 consistent = !equal; 0226 } 0227 else 0228 { 0229 /* If the equal boolean is true the line content must be the same, 0230 * if the line content is different the boolean should be false, 0231 * but other than that we can't be sure: 0232 * - if the line content is the same the boolean may not be true because 0233 * GNU diff may have put that line as a removal in the first file and 0234 * an addition in the second. 0235 * - also the comparison this test does between lines considers all 0236 * whitespace equal, while GNU diff doesn't (for instance U+0020 vs U+00A0) 0237 */ 0238 if(equal) 0239 { 0240 consistent = (line1Text == line2Text); 0241 } 0242 else if (line1Text != line2Text) 0243 { 0244 consistent = !equal; 0245 } 0246 else 0247 { 0248 consistent = true; 0249 } 0250 0251 } 0252 return consistent; 0253 } 0254 0255 bool runTest(QString file1, QString file2, QString file3, QString expectedResultFile, QString actualResultFile, int maxLength) 0256 { 0257 gOptions = std::make_unique < Options(); 0258 Diff3LineList actualDiff3LineList, expectedDiff3LineList; 0259 QTextCodec *p_codec = QTextCodec::codecForName("UTF-8"); 0260 QTextStream out(stdout); 0261 0262 gOptions->m_bIgnoreCase = false; 0263 gOptions->m_bDiff3AlignBC = true; 0264 0265 SourceData m_sd1, m_sd2, m_sd3; 0266 0267 QString msgprefix = "Running test with "; 0268 QString filepattern = QString(file1).replace("_base.", "_*."); 0269 QString msgsuffix = QString("...%1").arg("", maxLength - filepattern.length()); 0270 out << msgprefix << filepattern << msgsuffix; 0271 if(verbose) 0272 { 0273 out << endl; 0274 } 0275 out.flush(); 0276 0277 m_sd1.setFilename(file1); 0278 m_sd1.readAndPreprocess(p_codec, false); 0279 0280 m_sd2.setFilename(file2); 0281 m_sd2.readAndPreprocess(p_codec, false); 0282 0283 m_sd3.setFilename(file3); 0284 m_sd3.readAndPreprocess(p_codec, false); 0285 0286 determineFileAlignment(m_sd1, m_sd2, m_sd3, actualDiff3LineList); 0287 0288 loadExpectedAlignmentFile(expectedResultFile, expectedDiff3LineList); 0289 0290 Diff3LineList::iterator p_actual = actualDiff3LineList.begin(); 0291 Diff3LineList::iterator p_expected = expectedDiff3LineList.begin(); 0292 bool equal = true; 0293 bool sequenceError = false; 0294 bool consistencyError = false; 0295 0296 equal = (actualDiff3LineList.size() == expectedDiff3LineList.size()); 0297 0298 int latestLineA = -1; 0299 int latestLineB = -1; 0300 int latestLineC = -1; 0301 while(equal && (p_actual != actualDiff3LineList.end())) 0302 { 0303 /* Check if all line numbers are in sequence */ 0304 if(p_actual->getLineA().isValid()) 0305 { 0306 if(p_actual->getLineA() <= latestLineA) 0307 { 0308 sequenceError = true; 0309 } 0310 else 0311 { 0312 latestLineA = p_actual->getLineA(); 0313 } 0314 } 0315 if(p_actual->getLineB().isValid()) 0316 { 0317 if(p_actual->getLineB() <= latestLineB) 0318 { 0319 sequenceError = true; 0320 } 0321 else 0322 { 0323 latestLineB = p_actual->getLineB(); 0324 } 0325 } 0326 if(p_actual->getLineC().isValid()) 0327 { 0328 if(p_actual->getLineC() <= latestLineC) 0329 { 0330 sequenceError = true; 0331 } 0332 else 0333 { 0334 latestLineC = p_actual->getLineC(); 0335 } 0336 } 0337 0338 /* Check if the booleans that indicate if lines are equal are consistent with the content of the lines */ 0339 QString lineAText = (!p_actual->getLineA().isValid()) ? "" : getLineFromSourceData(m_sd1, p_actual->getLineA()).simplified().replace(" ", ""); 0340 QString lineBText = (!p_actual->getLineB().isValid()) ? "" : getLineFromSourceData(m_sd2, p_actual->getLineB()).simplified().replace(" ", ""); 0341 QString lineCText = (!p_actual->getLineC().isValid()) ? "" : getLineFromSourceData(m_sd3, p_actual->getLineC()).simplified().replace(" ", ""); 0342 0343 if(!dataIsConsistent(p_actual->getLineA(), lineAText, p_actual->getLineB(), lineBText, p_actual->isEqualAB())) 0344 { 0345 if(verbose) out << "inconsistency: line " << p_actual->getLineA() << " of A vs line " << p_actual->getLineB() << " of B" << endl; 0346 consistencyError = true; 0347 } 0348 if(!dataIsConsistent(p_actual->getLineB(), lineBText, p_actual->getLineC(), lineCText, p_actual->isEqualBC())) 0349 { 0350 if(verbose) out << "inconsistency: line " << p_actual->getLineB() << " of B vs line " << p_actual->getLineC() << " of C" << endl; 0351 consistencyError = true; 0352 } 0353 if(!dataIsConsistent(p_actual->getLineA(), lineAText, p_actual->getLineC(), lineCText, p_actual->isEqualAC())) 0354 { 0355 if(verbose) out << "inconsistency: line " << p_actual->getLineA() << " of A vs line " << p_actual->getLineC() << " of C" << endl; 0356 consistencyError = true; 0357 } 0358 0359 /* Check if the actual output of the algorithm is equal to the expected output */ 0360 equal = (p_actual->getLineA() == p_expected->getLineA()) && 0361 (p_actual->getLineB() == p_expected->getLineB()) && 0362 (p_actual->getLineC() == p_expected->getLineC()); 0363 p_actual++; 0364 p_expected++; 0365 } 0366 0367 if(sequenceError) 0368 { 0369 out << "NOK" << endl; 0370 0371 out << "Actual result has incorrectly sequenced line numbers:" << endl; 0372 out << "----------------------------------------------------------------------------------------------" << endl; 0373 printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3); 0374 } 0375 else if(consistencyError) 0376 { 0377 out << "NOK" << endl; 0378 0379 out << "Actual result has inconsistent equality booleans:" << endl; 0380 out << "----------------------------------------------------------------------------------------------" << endl; 0381 printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3, true); 0382 } 0383 else if(equal) 0384 { 0385 out << "OK" << endl; 0386 } 0387 else 0388 { 0389 out << "NOK" << endl; 0390 0391 writeActualAlignmentFile(actualResultFile, actualDiff3LineList); 0392 0393 out << "Actual result (written to " << actualResultFile << "):" << endl; 0394 out << "----------------------------------------------------------------------------------------------" << endl; 0395 printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3); 0396 out << "----------------------------------------------------------------------------------------------" << endl; 0397 out << "Expected result:" << endl; 0398 out << "----------------------------------------------------------------------------------------------" << endl; 0399 printDiff3List(expectedDiff3LineList, m_sd1, m_sd2, m_sd3); 0400 out << "----------------------------------------------------------------------------------------------" << endl; 0401 } 0402 0403 return equal; 0404 } 0405 0406 0407 QStringList gettestdatafiles(QString testdir) 0408 { 0409 QStringList baseFilePaths; 0410 QTextStream out(stdout); 0411 QStringList nameFilter; 0412 nameFilter << "*_base.*"; 0413 0414 QDir testdatadir(testdir); 0415 0416 QStringList baseFileNames = testdatadir.entryList(nameFilter, QDir::Files, QDir::Name); 0417 QListIterator<QString> file_it(baseFileNames); 0418 while(file_it.hasNext()) 0419 { 0420 baseFilePaths.append(testdir + "/" + file_it.next()); 0421 } 0422 out << testdir << ": " << baseFilePaths.size() << " files" << endl; 0423 0424 0425 QStringList subdirs = testdatadir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); 0426 QListIterator<QString> dir_it(subdirs); 0427 0428 while (dir_it.hasNext()) 0429 { 0430 QString subdir = dir_it.next(); 0431 QStringList subdirBaseFilePaths = gettestdatafiles(testdir + "/" + subdir); 0432 0433 baseFilePaths.append(subdirBaseFilePaths); 0434 } 0435 0436 return baseFilePaths; 0437 } 0438 0439 0440 int main(int argc, char *argv[]) 0441 { 0442 bool allOk = true; 0443 int maxLength = 0; 0444 QTextStream out(stdout); 0445 QDir testdatadir("testdata"); 0446 0447 /* Print data at various steps in the algorithm to get an idea where to look for the root cause of a failing test */ 0448 if((argc == 2) && (!strcmp(argv[1], "-v"))) 0449 { 0450 verbose = true; 0451 } 0452 0453 QStringList baseFiles = gettestdatafiles("testdata"); 0454 QListIterator<QString> it(baseFiles); 0455 0456 for (int i = 0; i < baseFiles.size(); i++) 0457 { 0458 maxLength = std::max(baseFiles.at(i).length(), maxLength); 0459 } 0460 maxLength += testdatadir.path().length() + 1; 0461 0462 while (it.hasNext()) 0463 { 0464 QString fileName = it.next(); 0465 0466 QRegularExpression baseFileRegExp("(.*)_base\\.(.*)"); 0467 QRegularExpressionMatch match = baseFileRegExp.match(fileName); 0468 0469 QString prefix = match.captured(1); 0470 QString suffix = match.captured(2); 0471 0472 QString contrib1FileName(prefix + "_contrib1." + suffix); 0473 QString contrib2FileName(prefix + "_contrib2." + suffix); 0474 QString expectedResultFileName(prefix + "_expected_result." + suffix); 0475 QString actualResultFileName(prefix + "_actual_result." + suffix); 0476 0477 if(QFile(contrib1FileName).exists() && 0478 QFile(contrib2FileName).exists() && 0479 QFile(expectedResultFileName).exists()) 0480 { 0481 bool ok = runTest(fileName, contrib1FileName, contrib2FileName, expectedResultFileName, actualResultFileName, maxLength); 0482 0483 allOk = allOk && ok; 0484 } 0485 else 0486 { 0487 out << "Skipping " << fileName << " " << contrib1FileName << " " << contrib2FileName << " " << expectedResultFileName << " " << endl; 0488 } 0489 } 0490 0491 out << (allOk ? "All OK" : "Not all OK") << endl; 0492 0493 return allOk ? 0 : -1; 0494 }