Merge pull request #8423 from imapp-pl/yul-phaser-population-and-algorithm-options

[yul-phaser] Population and algorithm options
This commit is contained in:
chriseth
2020-03-18 16:05:38 +01:00
committed by GitHub
17 changed files with 1023 additions and 32 deletions
+3
View File
@@ -151,6 +151,7 @@ set(yul_phaser_sources
yulPhaser/GeneticAlgorithms.cpp
yulPhaser/Mutations.cpp
yulPhaser/PairSelections.cpp
yulPhaser/Phaser.cpp
yulPhaser/Population.cpp
yulPhaser/Program.cpp
yulPhaser/Selections.cpp
@@ -160,11 +161,13 @@ set(yul_phaser_sources
# My current workaround is just to include its source files here but this introduces
# unnecessary duplication. Create a library or find a way to reuse the list in both places.
../tools/yulPhaser/AlgorithmRunner.cpp
../tools/yulPhaser/Common.cpp
../tools/yulPhaser/Chromosome.cpp
../tools/yulPhaser/FitnessMetrics.cpp
../tools/yulPhaser/GeneticAlgorithms.cpp
../tools/yulPhaser/Mutations.cpp
../tools/yulPhaser/PairSelections.cpp
../tools/yulPhaser/Phaser.cpp
../tools/yulPhaser/Population.cpp
../tools/yulPhaser/Program.cpp
../tools/yulPhaser/Selections.cpp
+147 -3
View File
@@ -18,9 +18,11 @@
#include <test/yulPhaser/TestHelpers.h>
#include <tools/yulPhaser/AlgorithmRunner.h>
#include <tools/yulPhaser/Common.h>
#include <libsolutil/CommonIO.h>
#include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/tools/output_test_stream.hpp>
@@ -29,10 +31,12 @@ using namespace boost::unit_test::framework;
using namespace boost::test_tools;
using namespace solidity::util;
namespace fs = boost::filesystem;
namespace solidity::phaser::test
{
class DummyAlgorithm: public GeneticAlgorithm
class CountingAlgorithm: public GeneticAlgorithm
{
public:
using GeneticAlgorithm::GeneticAlgorithm;
@@ -45,6 +49,16 @@ public:
size_t m_currentRound = 0;
};
class RandomisingAlgorithm: public GeneticAlgorithm
{
public:
using GeneticAlgorithm::GeneticAlgorithm;
Population runNextRound(Population _population) override
{
return Population::makeRandom(_population.fitnessMetric(), _population.individuals().size(), 10, 20);
}
};
class AlgorithmRunnerFixture
{
protected:
@@ -53,6 +67,25 @@ protected:
AlgorithmRunner::Options m_options;
};
class AlgorithmRunnerAutosaveFixture: public AlgorithmRunnerFixture
{
public:
static vector<string> chromosomeStrings(Population const& _population)
{
vector<string> lines;
for (auto const& individual: _population.individuals())
lines.push_back(toString(individual.chromosome));
return lines;
}
protected:
TemporaryDirectory m_tempDir;
string const m_autosavePath = m_tempDir.memberPath("population-autosave.txt");
Population const m_population = Population::makeRandom(m_fitnessMetric, 5, 0, 20);
RandomisingAlgorithm m_algorithm;
};
BOOST_AUTO_TEST_SUITE(Phaser)
BOOST_AUTO_TEST_SUITE(AlgorithmRunnerTest)
@@ -60,7 +93,8 @@ BOOST_FIXTURE_TEST_CASE(run_should_call_runNextRound_once_per_round, AlgorithmRu
{
m_options.maxRounds = 5;
AlgorithmRunner runner(Population(m_fitnessMetric), m_options, m_output);
DummyAlgorithm algorithm;
CountingAlgorithm algorithm;
BOOST_TEST(algorithm.m_currentRound == 0);
runner.run(algorithm);
@@ -82,7 +116,7 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_the_top_chromosome, AlgorithmRunnerFixt
m_output
);
DummyAlgorithm algorithm;
CountingAlgorithm algorithm;
BOOST_TEST(m_output.is_empty());
runner.run(algorithm);
@@ -93,6 +127,116 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_the_top_chromosome, AlgorithmRunnerFixt
BOOST_TEST(countSubstringOccurrences(m_output.str(), toString(runner.population().individuals()[0].chromosome)) == 4);
}
BOOST_FIXTURE_TEST_CASE(run_should_save_initial_population_to_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture)
{
m_options.maxRounds = 0;
m_options.populationAutosaveFile = m_autosavePath;
AlgorithmRunner runner(m_population, m_options, m_output);
assert(!fs::exists(m_autosavePath));
runner.run(m_algorithm);
assert(runner.population() == m_population);
BOOST_TEST(fs::is_regular_file(m_autosavePath));
BOOST_TEST(readLinesFromFile(m_autosavePath) == chromosomeStrings(runner.population()));
}
BOOST_FIXTURE_TEST_CASE(run_should_save_population_to_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture)
{
m_options.maxRounds = 1;
m_options.populationAutosaveFile = m_autosavePath;
AlgorithmRunner runner(m_population, m_options, m_output);
assert(!fs::exists(m_autosavePath));
runner.run(m_algorithm);
assert(runner.population() != m_population);
BOOST_TEST(fs::is_regular_file(m_autosavePath));
BOOST_TEST(readLinesFromFile(m_autosavePath) == chromosomeStrings(runner.population()));
}
BOOST_FIXTURE_TEST_CASE(run_should_overwrite_existing_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture)
{
m_options.maxRounds = 5;
m_options.populationAutosaveFile = m_autosavePath;
AlgorithmRunner runner(m_population, m_options, m_output);
assert(!fs::exists(m_autosavePath));
vector<string> originalContent = {"Original content"};
{
ofstream tmpFile(m_autosavePath);
tmpFile << originalContent[0] << endl;
}
assert(fs::exists(m_autosavePath));
assert(readLinesFromFile(m_autosavePath) == originalContent);
runner.run(m_algorithm);
BOOST_TEST(fs::is_regular_file(m_autosavePath));
BOOST_TEST(readLinesFromFile(m_autosavePath) != originalContent);
}
BOOST_FIXTURE_TEST_CASE(run_should_not_save_population_to_file_if_autosave_file_not_specified, AlgorithmRunnerAutosaveFixture)
{
m_options.maxRounds = 5;
m_options.populationAutosaveFile = nullopt;
AlgorithmRunner runner(m_population, m_options, m_output);
assert(!fs::exists(m_autosavePath));
runner.run(m_algorithm);
BOOST_TEST(!fs::exists(m_autosavePath));
}
BOOST_FIXTURE_TEST_CASE(run_should_randomise_duplicate_chromosomes_if_requested, AlgorithmRunnerFixture)
{
Chromosome duplicate("afc");
Population population(m_fitnessMetric, {duplicate, duplicate, duplicate});
CountingAlgorithm algorithm;
m_options.maxRounds = 1;
m_options.randomiseDuplicates = true;
m_options.minChromosomeLength = 50;
m_options.maxChromosomeLength = 50;
AlgorithmRunner runner(population, m_options, m_output);
runner.run(algorithm);
auto const& newIndividuals = runner.population().individuals();
BOOST_TEST(newIndividuals.size() == 3);
BOOST_TEST((
newIndividuals[0].chromosome == duplicate ||
newIndividuals[1].chromosome == duplicate ||
newIndividuals[2].chromosome == duplicate
));
BOOST_TEST(newIndividuals[0] != newIndividuals[1]);
BOOST_TEST(newIndividuals[0] != newIndividuals[2]);
BOOST_TEST(newIndividuals[1] != newIndividuals[2]);
BOOST_TEST((newIndividuals[0].chromosome.length() == 50 || newIndividuals[0].chromosome == duplicate));
BOOST_TEST((newIndividuals[1].chromosome.length() == 50 || newIndividuals[1].chromosome == duplicate));
BOOST_TEST((newIndividuals[2].chromosome.length() == 50 || newIndividuals[2].chromosome == duplicate));
}
BOOST_FIXTURE_TEST_CASE(run_should_not_randomise_duplicate_chromosomes_if_not_requested, AlgorithmRunnerFixture)
{
Chromosome duplicate("afc");
Population population(m_fitnessMetric, {duplicate, duplicate, duplicate});
CountingAlgorithm algorithm;
m_options.maxRounds = 1;
m_options.randomiseDuplicates = false;
AlgorithmRunner runner(population, m_options, m_output);
runner.run(algorithm);
BOOST_TEST(runner.population().individuals().size() == 3);
BOOST_TEST(runner.population().individuals()[0].chromosome == duplicate);
BOOST_TEST(runner.population().individuals()[1].chromosome == duplicate);
BOOST_TEST(runner.population().individuals()[2].chromosome == duplicate);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
+20
View File
@@ -15,6 +15,8 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <test/yulPhaser/TestHelpers.h>
#include <tools/yulPhaser/Common.h>
#include <libsolutil/CommonData.h>
@@ -22,6 +24,7 @@
#include <boost/test/unit_test.hpp>
#include <boost/test/tools/output_test_stream.hpp>
#include <fstream>
#include <sstream>
#include <string>
@@ -32,6 +35,12 @@ using namespace solidity::util;
namespace solidity::phaser::test
{
class ReadLinesFromFileFixture
{
protected:
TemporaryDirectory m_tempDir;
};
namespace
{
@@ -60,6 +69,17 @@ map<string, TestEnum> const StringToTestEnumMap = invertMap(TestEnumToStringMap)
BOOST_AUTO_TEST_SUITE(Phaser)
BOOST_AUTO_TEST_SUITE(CommonTest)
BOOST_FIXTURE_TEST_CASE(readLinesFromFile_should_return_all_lines_from_a_text_file_as_strings_without_newlines, ReadLinesFromFileFixture)
{
{
ofstream tmpFile(m_tempDir.memberPath("test-file.txt"));
tmpFile << endl << "Line 1" << endl << endl << endl << "Line 2" << endl << "#" << endl << endl;
}
vector<string> lines = readLinesFromFile(m_tempDir.memberPath("test-file.txt"));
BOOST_TEST((lines == vector<string>{"", "Line 1", "", "", "Line 2", "#", ""}));
}
BOOST_AUTO_TEST_CASE(deserializeChoice_should_convert_string_to_enum)
{
istringstream aStream("a");
+290
View File
@@ -0,0 +1,290 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <test/yulPhaser/TestHelpers.h>
#include <tools/yulPhaser/Exceptions.h>
#include <tools/yulPhaser/Phaser.h>
#include <liblangutil/CharStream.h>
#include <libsolutil/CommonIO.h>
#include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp>
#include <algorithm>
using namespace std;
using namespace solidity::util;
using namespace solidity::langutil;
namespace fs = boost::filesystem;
namespace solidity::phaser::test
{
class GeneticAlgorithmFactoryFixture
{
protected:
GeneticAlgorithmFactory::Options m_options = {
/* algorithm = */ Algorithm::Random,
/* minChromosomeLength = */ 50,
/* maxChromosomeLength = */ 100,
/* randomElitePoolSize = */ 0.5,
/* gewepMutationPoolSize = */ 0.1,
/* gewepCrossoverPoolSize = */ 0.1,
/* gewepRandomisationChance = */ 0.6,
/* gewepDeletionVsAdditionChance = */ 0.3,
/* gewepGenesToRandomise = */ 0.4,
/* gewepGenesToAddOrDelete = */ 0.2,
};
};
class FitnessMetricFactoryFixture
{
protected:
CharStream m_sourceStream = CharStream("{}", "");
Program m_program = get<Program>(Program::load(m_sourceStream));
FitnessMetricFactory::Options m_options = {
/* chromosomeRepetitions = */ 1,
};
};
class PoulationFactoryFixture
{
protected:
shared_ptr<FitnessMetric> m_fitnessMetric = make_shared<ChromosomeLengthMetric>();
PopulationFactory::Options m_options = {
/* minChromosomeLength = */ 0,
/* maxChromosomeLength = */ 0,
/* population = */ {},
/* randomPopulation = */ {},
/* populationFromFile = */ {},
};
};
BOOST_AUTO_TEST_SUITE(Phaser)
BOOST_AUTO_TEST_SUITE(PhaserTest)
BOOST_AUTO_TEST_SUITE(GeneticAlgorithmFactoryTest)
BOOST_FIXTURE_TEST_CASE(build_should_select_the_right_algorithm_and_pass_the_options_to_it, GeneticAlgorithmFactoryFixture)
{
m_options.algorithm = Algorithm::Random;
unique_ptr<GeneticAlgorithm> algorithm1 = GeneticAlgorithmFactory::build(m_options, 100);
BOOST_REQUIRE(algorithm1 != nullptr);
auto randomAlgorithm = dynamic_cast<RandomAlgorithm*>(algorithm1.get());
BOOST_REQUIRE(randomAlgorithm != nullptr);
BOOST_TEST(randomAlgorithm->options().elitePoolSize == m_options.randomElitePoolSize.value());
BOOST_TEST(randomAlgorithm->options().minChromosomeLength == m_options.minChromosomeLength);
BOOST_TEST(randomAlgorithm->options().maxChromosomeLength == m_options.maxChromosomeLength);
m_options.algorithm = Algorithm::GEWEP;
unique_ptr<GeneticAlgorithm> algorithm2 = GeneticAlgorithmFactory::build(m_options, 100);
BOOST_REQUIRE(algorithm2 != nullptr);
auto gewepAlgorithm = dynamic_cast<GenerationalElitistWithExclusivePools*>(algorithm2.get());
BOOST_REQUIRE(gewepAlgorithm != nullptr);
BOOST_TEST(gewepAlgorithm->options().mutationPoolSize == m_options.gewepMutationPoolSize);
BOOST_TEST(gewepAlgorithm->options().crossoverPoolSize == m_options.gewepCrossoverPoolSize);
BOOST_TEST(gewepAlgorithm->options().randomisationChance == m_options.gewepRandomisationChance);
BOOST_TEST(gewepAlgorithm->options().deletionVsAdditionChance == m_options.gewepDeletionVsAdditionChance);
BOOST_TEST(gewepAlgorithm->options().percentGenesToRandomise == m_options.gewepGenesToRandomise.value());
BOOST_TEST(gewepAlgorithm->options().percentGenesToAddOrDelete == m_options.gewepGenesToAddOrDelete.value());
}
BOOST_FIXTURE_TEST_CASE(build_should_set_random_algorithm_elite_pool_size_based_on_population_size_if_not_specified, GeneticAlgorithmFactoryFixture)
{
m_options.algorithm = Algorithm::Random;
m_options.randomElitePoolSize = nullopt;
unique_ptr<GeneticAlgorithm> algorithm = GeneticAlgorithmFactory::build(m_options, 100);
BOOST_REQUIRE(algorithm != nullptr);
auto randomAlgorithm = dynamic_cast<RandomAlgorithm*>(algorithm.get());
BOOST_REQUIRE(randomAlgorithm != nullptr);
BOOST_TEST(randomAlgorithm->options().elitePoolSize == 1.0 / 100.0);
}
BOOST_FIXTURE_TEST_CASE(build_should_set_gewep_mutation_percentages_based_on_maximum_chromosome_length_if_not_specified, GeneticAlgorithmFactoryFixture)
{
m_options.algorithm = Algorithm::GEWEP;
m_options.gewepGenesToRandomise = nullopt;
m_options.gewepGenesToAddOrDelete = nullopt;
m_options.maxChromosomeLength = 125;
unique_ptr<GeneticAlgorithm> algorithm = GeneticAlgorithmFactory::build(m_options, 100);
BOOST_REQUIRE(algorithm != nullptr);
auto gewepAlgorithm = dynamic_cast<GenerationalElitistWithExclusivePools*>(algorithm.get());
BOOST_REQUIRE(gewepAlgorithm != nullptr);
BOOST_TEST(gewepAlgorithm->options().percentGenesToRandomise == 1.0 / 125.0);
BOOST_TEST(gewepAlgorithm->options().percentGenesToAddOrDelete == 1.0 / 125.0);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(FitnessMetricFactoryTest)
BOOST_FIXTURE_TEST_CASE(build_should_create_metric_of_the_right_type, FitnessMetricFactoryFixture)
{
unique_ptr<FitnessMetric> metric = FitnessMetricFactory::build(m_options, m_program);
BOOST_REQUIRE(metric != nullptr);
auto programSizeMetric = dynamic_cast<ProgramSize*>(metric.get());
BOOST_REQUIRE(programSizeMetric != nullptr);
BOOST_TEST(toString(programSizeMetric->program()) == toString(m_program));
}
BOOST_FIXTURE_TEST_CASE(build_should_respect_chromosome_repetitions_option, FitnessMetricFactoryFixture)
{
m_options.chromosomeRepetitions = 5;
unique_ptr<FitnessMetric> metric = FitnessMetricFactory::build(m_options, m_program);
BOOST_REQUIRE(metric != nullptr);
auto programSizeMetric = dynamic_cast<ProgramSize*>(metric.get());
BOOST_REQUIRE(programSizeMetric != nullptr);
BOOST_TEST(programSizeMetric->repetitionCount() == m_options.chromosomeRepetitions);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(PopulationFactoryTest)
BOOST_FIXTURE_TEST_CASE(build_should_create_an_empty_population_if_no_specific_options_given, PoulationFactoryFixture)
{
m_options.population = {};
m_options.randomPopulation = {};
m_options.populationFromFile = {};
BOOST_TEST(
PopulationFactory::build(m_options, m_fitnessMetric) ==
Population(m_fitnessMetric, vector<Chromosome>{})
);
}
BOOST_FIXTURE_TEST_CASE(build_should_respect_population_option, PoulationFactoryFixture)
{
m_options.population = {"a", "afc", "xadd"};
BOOST_TEST(
PopulationFactory::build(m_options, m_fitnessMetric) ==
Population(m_fitnessMetric, {Chromosome("a"), Chromosome("afc"), Chromosome("xadd")})
);
}
BOOST_FIXTURE_TEST_CASE(build_should_respect_random_population_option, PoulationFactoryFixture)
{
m_options.randomPopulation = {5, 3, 2};
m_options.minChromosomeLength = 5;
m_options.maxChromosomeLength = 10;
auto population = PopulationFactory::build(m_options, m_fitnessMetric);
BOOST_TEST(population.individuals().size() == 10);
BOOST_TEST(all_of(
population.individuals().begin(),
population.individuals().end(),
[](auto const& individual){ return 5 <= individual.chromosome.length() && individual.chromosome.length() <= 10; }
));
}
BOOST_FIXTURE_TEST_CASE(build_should_respect_population_from_file_option, PoulationFactoryFixture)
{
map<string, vector<string>> fileContent = {
{"a.txt", {"a", "fff", "", "jxccLTa"}},
{"b.txt", {}},
{"c.txt", {""}},
{"d.txt", {"c", "T"}},
};
TemporaryDirectory tempDir;
for (auto const& [fileName, chromosomes]: fileContent)
{
ofstream tmpFile(tempDir.memberPath(fileName));
for (auto const& chromosome: chromosomes)
tmpFile << chromosome << endl;
m_options.populationFromFile.push_back(tempDir.memberPath(fileName));
}
BOOST_TEST(
PopulationFactory::build(m_options, m_fitnessMetric) ==
Population(m_fitnessMetric, {
Chromosome("a"),
Chromosome("fff"),
Chromosome(""),
Chromosome("jxccLTa"),
Chromosome(""),
Chromosome("c"),
Chromosome("T"),
})
);
}
BOOST_FIXTURE_TEST_CASE(build_should_throw_FileOpenError_if_population_file_does_not_exist, PoulationFactoryFixture)
{
m_options.populationFromFile = {"a-file-that-does-not-exist.abcdefgh"};
assert(!fs::exists(m_options.populationFromFile[0]));
BOOST_CHECK_THROW(PopulationFactory::build(m_options, m_fitnessMetric), FileOpenError);
}
BOOST_FIXTURE_TEST_CASE(build_should_combine_populations_from_all_sources, PoulationFactoryFixture)
{
TemporaryDirectory tempDir;
{
ofstream tmpFile(tempDir.memberPath("population.txt"));
tmpFile << "axc" << endl << "fcL" << endl;
}
m_options.population = {"axc", "fcL"};
m_options.randomPopulation = {2};
m_options.populationFromFile = {tempDir.memberPath("population.txt")};
m_options.minChromosomeLength = 3;
m_options.maxChromosomeLength = 3;
auto population = PopulationFactory::build(m_options, m_fitnessMetric);
auto begin = population.individuals().begin();
auto end = population.individuals().end();
BOOST_TEST(population.individuals().size() == 6);
BOOST_TEST(all_of(begin, end, [](auto const& individual){ return individual.chromosome.length() == 3; }));
BOOST_TEST(count(begin, end, Individual(Chromosome("axc"), *m_fitnessMetric)) >= 2);
BOOST_TEST(count(begin, end, Individual(Chromosome("fcL"), *m_fitnessMetric)) >= 2);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(ProgramFactoryTest)
BOOST_AUTO_TEST_CASE(build_should_load_program_from_file)
{
TemporaryDirectory tempDir;
{
ofstream tmpFile(tempDir.memberPath("program.yul"));
tmpFile << "{}" << endl;
}
ProgramFactory::Options options{/* inputFile = */ tempDir.memberPath("program.yul")};
CharStream expectedProgramSource("{}", "");
auto program = ProgramFactory::build(options);
BOOST_TEST(toString(program) == toString(get<Program>(Program::load(expectedProgramSource))));
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
}
+40
View File
@@ -19,12 +19,18 @@
#include <libyul/optimiser/Suite.h>
#include <boost/filesystem.hpp>
#include <regex>
#include <iostream>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
using namespace solidity::phaser;
using namespace solidity::phaser::test;
namespace fs = boost::filesystem;
function<Mutation> phaser::test::wholeChromosomeReplacement(Chromosome _newChromosome)
{
@@ -71,6 +77,40 @@ size_t phaser::test::countDifferences(Chromosome const& _chromosome1, Chromosome
return count + abs(static_cast<int>(_chromosome1.length() - _chromosome2.length()));
}
TemporaryDirectory::TemporaryDirectory(std::string const& _prefix):
m_path((fs::temp_directory_path() / fs::unique_path(_prefix + "%%%%-%%%%-%%%%-%%%%")).string())
{
// Prefix should just be a file name and not contain anything that would make us step out of /tmp.
assert(fs::path(_prefix) == fs::path(_prefix).stem());
fs::create_directory(m_path);
}
TemporaryDirectory::~TemporaryDirectory()
{
// A few paranoid sanity checks just to be extra sure we're not deleting someone's homework.
assert(m_path.find(fs::temp_directory_path().string()) == 0);
assert(fs::path(m_path) != fs::temp_directory_path());
assert(fs::path(m_path) != fs::path(m_path).root_path());
assert(!fs::path(m_path).empty());
boost::system::error_code errorCode;
uintmax_t numRemoved = fs::remove_all(m_path, errorCode);
if (errorCode.value() != boost::system::errc::success)
{
cerr << "Failed to completely remove temporary directory '" << m_path << "'. ";
cerr << "Only " << numRemoved << " files were actually removed." << endl;
cerr << "Reason: " << errorCode.message() << endl;
}
}
string TemporaryDirectory::memberPath(string const& _relativePath) const
{
assert(fs::path(_relativePath).is_relative());
return (fs::path(m_path) / _relativePath).string();
}
string phaser::test::stripWhitespace(string const& input)
{
regex whitespaceRegex("\\s+");
+25
View File
@@ -79,6 +79,31 @@ size_t countDifferences(Chromosome const& _chromosome1, Chromosome const& _chrom
/// integers.
std::map<std::string, size_t> enumerateOptmisationSteps();
// FILESYSTEM UTILITIES
/**
* An object that creates a unique temporary directory and automatically deletes it and its
* content upon being destroyed.
*
* The directory is guaranteed to be newly created and empty. Directory names are generated
* randomly. If a directory with the same name already exists (very unlikely but possible) the
* object won't reuse it and will fail with an exception instead.
*/
class TemporaryDirectory
{
public:
TemporaryDirectory(std::string const& _prefix = "yul-phaser-test-");
~TemporaryDirectory();
std::string const& path() const { return m_path; }
/// Converts a path relative to the directory held by the object into an absolute one.
std::string memberPath(std::string const& _relativePath) const;
private:
std::string m_path;
};
// STRING UTILITIES
/// Returns the input string with all the whitespace characters (spaces, line endings, etc.) removed.
+61
View File
@@ -19,14 +19,18 @@
#include <libyul/optimiser/Suite.h>
#include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp>
#include <fstream>
#include <set>
using namespace std;
using namespace solidity::yul;
using namespace boost::test_tools;
namespace fs = boost::filesystem;
namespace solidity::phaser::test
{
@@ -114,6 +118,63 @@ BOOST_AUTO_TEST_CASE(enumerateOptimisationSteps_should_assing_indices_to_all_ava
}
}
BOOST_AUTO_TEST_CASE(TemporaryDirectory_should_create_and_delete_a_unique_and_empty_directory)
{
fs::path dirPath;
{
TemporaryDirectory tempDir("temporary-directory-test-");
dirPath = tempDir.path();
BOOST_TEST(dirPath.stem().string().find("temporary-directory-test-") == 0);
BOOST_TEST(fs::equivalent(dirPath.parent_path(), fs::temp_directory_path()));
BOOST_TEST(fs::is_directory(dirPath));
BOOST_TEST(fs::is_empty(dirPath));
}
BOOST_TEST(!fs::exists(dirPath));
}
BOOST_AUTO_TEST_CASE(TemporaryDirectory_should_delete_its_directory_even_if_not_empty)
{
fs::path dirPath;
{
TemporaryDirectory tempDir("temporary-directory-test-");
dirPath = tempDir.path();
BOOST_TEST(fs::is_directory(dirPath));
{
ofstream tmpFile((dirPath / "test-file.txt").string());
tmpFile << "Delete me!" << endl;
}
assert(fs::is_regular_file(dirPath / "test-file.txt"));
}
BOOST_TEST(!fs::exists(dirPath / "test-file.txt"));
}
BOOST_AUTO_TEST_CASE(TemporaryDirectory_memberPath_should_construct_paths_relative_to_the_temporary_directory)
{
TemporaryDirectory tempDir("temporary-directory-test-");
BOOST_TEST(fs::equivalent(tempDir.memberPath(""), tempDir.path()));
BOOST_TEST(fs::equivalent(tempDir.memberPath("."), tempDir.path() / fs::path(".")));
BOOST_TEST(fs::equivalent(tempDir.memberPath(".."), tempDir.path() / fs::path("..")));
// NOTE: fs::equivalent() only works with paths that actually exist
{
ofstream file;
file.open(tempDir.memberPath("file.txt"), ios::out);
}
BOOST_TEST(fs::equivalent(tempDir.memberPath("file.txt"), tempDir.path() / fs::path("file.txt")));
{
fs::create_directories(tempDir.memberPath("a/b/"));
ofstream file;
file.open(tempDir.memberPath("a/b/file.txt"), ios::out);
}
BOOST_TEST(fs::equivalent(tempDir.memberPath("a/b/file.txt"), tempDir.path() / fs::path("a") / fs::path("b") / fs::path("file.txt")));
}
BOOST_AUTO_TEST_CASE(stripWhitespace_should_remove_all_whitespace_characters_from_a_string)
{
BOOST_TEST(stripWhitespace("") == "");