I've been learning about test-driven development recently.... I think we can effectively build a test suite together:
a) Get a dev with *nix to open a C::B project.
b) Add a debugging to print out the result of the conversion, using exclusively the wxFileName:
i.e.
Relative Common Top Level Path: _______
Base Path: ___________
List of paths and filenames:
/somepath/someotherpath/myfilename.ext
/somepath/someotherpath/anotherfilename.ext
...
'/somepath/someotherpath/myfilename.ext (CTLP)' => 'someotherpath/myfilename.ext'
'/somepath/someotherpath (CTLP)' => 'someotherpath'
'/somepath/someotherpath/myfilename.ext (Base Path)' => 'myfilename.ext'
'/athirdpath/myfilename.ext (CTLP)' => '../athirdpath/myfilename.ext'
'/athirdpath' => '../athirdpath'
And so on. Then, we can replicate the wxFilename's conversion for said path by simply storing the results in a string map.
c) Refactor our code so that it will be able to be covered by unit tests.
// This will be our interface for invoking wxFilename::MakeRelativeTo();
// also our interface for our wxFakeFilename so that we can pass it to our Cached Make Relative function.
class cbFilenameConverterInterface {
abstract void setBasePath(const wxString& basePath);
abstract wxString MakeRelative(const wxString& path1, const wxString& path2);
virtual void setFallBack(cbFilenameConverterInterface* fallback) {} // For cache misses
virtual void setPathSeparator(wxChar sep) {}
}
// Instead of directly calling wxFilename::MakeRelative, we'll use this.
class cbSlowButSureMakeRelative : public cbFilenameConverterInterface {
void setBasePath(const wxString& basePath) {} // wxFilename doesn't work this way
void wxString MakeRelative(const wxString& path1, const wxString& path2) {
wxFilename filename(path1);
return filename.makeRelativeTo(path2); // Or whatever
}
}
class MyPathCache: public cbFilenameConverterInterface {
...
// TODO: I need to modify my Path Cache code to adapt to this interface.
}
d) Add our mock converter for test cases.
class cbMockFilename: public cbFilenameConverterInterface {
public:
void setBasePath(const wxString& basePath);
wxString MakeRelative(const wxString& path1, const wxString& path2);
// Use this to insert all the results obtained via wxFilename.
// I need to modify my path cache to output (in debug) all the times wxFilename is invoked so that we can later insert them manually.
void insertTestCase(const wxString& basePath, const wxString& path1, const wxString& path2, const wxString& result);
void clear();
protected:
wxString getTestCase(const wxString& basePath, const wxString& path1, const wxString& path2);
wxString m_BasePath;
bool m_CaseNotFound; // set or Reset everytime getTestCase is invoked.
// TODO: Insert here storage objects for our test cases
}
wxString cbMockFilename::MakeRelative(const wxString& path1, const wxString& path2) {
wxString result = this->getTestCase(this->m_BasePath, path1,path2);
if(this->m_CaseNotFound { // We should have a list of various basepaths in our test suit
// ALL test cases should have their correct results covered! (We can't test a function when we don't know what it's supposed to return)
debug("Error! '%s' -> '%s' are not covered by our test case!", path1, path2);
return wxString(_T('*ERROR*'));
} else {
return result;
}
}
e) We have everything ready to test!
(pseudocode)
void MakeRelativeUnitTest(const wxString basePath, vector<wxString>ourFilenames,map<wxString, wxString>ourExpectedResults, const wxChar sep, wxFilenameConverterInterface* func) {
bool testPassed = true;
cbFilenameConverterInterface* ourExperimentalObject = new MyPathCache();
cbFilenameConverterInterface* ourMockObject = new cbMockFilename();
ourExperimentalObject->setBasePath(basePath);
ourExperimentalObject->setPathSeparator(sep);
ourMockObject->setBasePath(basePath);
ourMockObject->setPathSeparator(sep);
ourExperimentalObject->setFallBack(ourMockObject); // Needed for files outside the CTLP.
wxString actualResult, expectedResult;
for(it = ourFilenames.begin(); it != ourFilenames.end(); ++it) {
expectedResult = cbMockFilename(*it);
actualResult = ourExperimentalClass->makeRelative(*it);
if(actualResult != expectedResult) {
debug("Test failed for: '%s'! Expected result: '%s'; Actual result: '%s',*itexpectedResult,actualResult);
testPassed = false;
break;
}
}
if(testPassed) {
debug("Test passed! Our Experimental Object is safe to use.");
}
}
IMHO, we should have done this years ago. So that the next time we get a rare bug, all we have to do is add it to our test cases; this way we can catch regression bugs much sooner.