1 /*============================================================================= 2 Copyright (c) 2001-2015 Joel de Guzman 3 4 Distributed under the Boost Software License, Version 1.0. (See accompanying 5 file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 =============================================================================*/ 7 #if !defined(BOOST_SPIRIT_X3_TEST_UTILITIES) 8 #define BOOST_SPIRIT_X3_TEST_UTILITIES 9 10 #include <boost/regex.hpp> 11 #include <boost/filesystem.hpp> 12 #include <boost/filesystem/fstream.hpp> 13 14 namespace boost { namespace spirit { namespace x3 { namespace testing 15 { 16 namespace fs = boost::filesystem; 17 18 //////////////////////////////////////////////////////////////////////////// 19 // compare 20 // 21 // Compares the contents of in with the template tem. The template 22 // may include embedded regular expressions marked up within re_prefix 23 // and re_suffix tags. For example, given the default RE markup, this 24 // template <%[0-9]+%> will match any integer in in. The function 25 // will return the first non-matching position. The flag full_match 26 // indicates a full match. It is possible for returned pos to be 27 // at the end of in (in.end()) while still returning full_match == 28 // false. In that case, we have a partial match. 29 //////////////////////////////////////////////////////////////////////////// 30 31 template <typename Iterator> 32 struct compare_result 33 { compare_resultboost::spirit::x3::testing::compare_result34 compare_result( 35 Iterator pos 36 , bool full_match 37 ) : pos(pos), full_match(full_match) {} 38 39 Iterator pos; 40 bool full_match; 41 }; 42 43 template <typename Range> 44 compare_result<typename Range::const_iterator> 45 compare( 46 Range const& in 47 , Range const& tem 48 , char const* re_prefix = "<%" 49 , char const* re_suffix = "%>" 50 ); 51 52 //////////////////////////////////////////////////////////////////////////// 53 // compare 54 // 55 // 1) Call f, given the contents of input_path loaded in a string. 56 // The result of calling f is the output string. 57 // 2) Compare the result of calling f with expected template 58 // file (expect_path) using the low-level compare utility 59 // abive 60 //////////////////////////////////////////////////////////////////////////// 61 template <typename F> 62 bool compare( 63 fs::path input_path, fs::path expect_path 64 , F f 65 , char const* re_prefix = "<%" 66 , char const* re_suffix = "%>" 67 ); 68 69 //////////////////////////////////////////////////////////////////////////// 70 // for_each_file 71 // 72 // For each *.input and *.expect file in a given directory, 73 // call the function f, passing in the *.input and *.expect paths. 74 //////////////////////////////////////////////////////////////////////////// 75 template <typename F> 76 int for_each_file(fs::path p, F f); 77 78 //////////////////////////////////////////////////////////////////////////// 79 // load_file 80 // 81 // Load file into a string. 82 //////////////////////////////////////////////////////////////////////////// 83 std::string load(fs::path p); 84 85 //////////////////////////////////////////////////////////////////////////// 86 // Implementation 87 //////////////////////////////////////////////////////////////////////////// 88 89 template <typename Iterator> is_regex(Iterator & first,Iterator last,std::string & re,char const * re_prefix,char const * re_suffix)90 inline bool is_regex( 91 Iterator& first 92 , Iterator last 93 , std::string& re 94 , char const* re_prefix 95 , char const* re_suffix 96 ) 97 { 98 boost::regex e(re_prefix + std::string("(.*?)") + re_suffix); 99 boost::match_results<Iterator> what; 100 if (boost::regex_search( 101 first, last, what, e 102 , boost::match_default | boost::match_continuous)) 103 { 104 re = what[1].str(); 105 first = what[0].second; 106 return true; 107 } 108 return false; 109 } 110 111 template <typename Range> 112 inline compare_result<typename Range::const_iterator> compare(Range const & in,Range const & tem,char const * re_prefix,char const * re_suffix)113 compare( 114 Range const& in 115 , Range const& tem 116 , char const* re_prefix 117 , char const* re_suffix 118 ) 119 { 120 typedef typename Range::const_iterator iter_t; 121 typedef compare_result<iter_t> compare_result_t; 122 123 iter_t in_first = in.begin(); 124 iter_t in_last = in.end(); 125 iter_t tem_first = tem.begin(); 126 iter_t tem_last = tem.end(); 127 std::string re; 128 129 while (in_first != in_last && tem_first != tem_last) 130 { 131 if (is_regex(tem_first, tem_last, re, re_prefix, re_suffix)) 132 { 133 boost::match_results<iter_t> what; 134 boost::regex e(re); 135 if (!boost::regex_search( 136 in_first, in_last, what, e 137 , boost::match_default | boost::match_continuous)) 138 { 139 // RE mismatch: exit now. 140 return compare_result_t(in_first, false); 141 } 142 else 143 { 144 // RE match: gobble the matching string. 145 in_first = what[0].second; 146 } 147 } 148 else 149 { 150 // Char by char comparison. Exit if we have a mismatch. 151 if (*in_first++ != *tem_first++) 152 return compare_result_t(in_first, false); 153 } 154 } 155 156 // Ignore trailing spaces in template 157 bool has_trailing_nonspaces = false; 158 while (tem_first != tem_last) 159 { 160 if (!std::isspace(*tem_first++)) 161 { 162 has_trailing_nonspaces = true; 163 break; 164 } 165 } 166 while (in_first != in_last) 167 { 168 if (!std::isspace(*in_first++)) 169 { 170 has_trailing_nonspaces = true; 171 break; 172 } 173 } 174 // return a full match only if the template is fully matched and if there 175 // are no more characters to match in the source 176 return compare_result_t(in_first, !has_trailing_nonspaces); 177 } 178 179 template <typename F> for_each_file(fs::path p,F f)180 inline int for_each_file(fs::path p, F f) 181 { 182 try 183 { 184 if (fs::exists(p) && fs::is_directory(p)) 185 { 186 for (auto i = fs::directory_iterator(p); i != fs::directory_iterator(); ++i) 187 { 188 auto ext = fs::extension(i->path()); 189 if (ext == ".input") 190 { 191 auto input_path = i->path(); 192 auto expect_path = input_path; 193 expect_path.replace_extension(".expect"); 194 f(input_path, expect_path); 195 } 196 } 197 } 198 else 199 { 200 std::cerr << "Directory: " << fs::absolute(p) << " does not exist." << std::endl; 201 return 1; 202 } 203 } 204 205 catch (const fs::filesystem_error& ex) 206 { 207 std::cerr << ex.what() << '\n'; 208 return 1; 209 } 210 return 0; 211 } 212 load(fs::path p)213 inline std::string load(fs::path p) 214 { 215 boost::filesystem::ifstream file(p); 216 if (!file) 217 return ""; 218 std::string contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); 219 return contents; 220 } 221 222 template <typename F> compare(fs::path input_path,fs::path expect_path,F f,char const * re_prefix,char const * re_suffix)223 inline bool compare( 224 fs::path input_path, fs::path expect_path 225 , F f 226 , char const* re_prefix 227 , char const* re_suffix 228 ) 229 { 230 std::string output = f(load(input_path), input_path); 231 std::string expected = load(expect_path); 232 233 auto result = compare(output, expected, re_prefix, re_suffix); 234 if (!result.full_match) 235 { 236 std::cout << "=============================================" << std::endl; 237 std::cout << "==== Mismatch Found:" << std::endl; 238 int line = 1; 239 int col = 1; 240 for (auto i = output.begin(); i != result.pos; ++i) 241 { 242 if (*i == '\n') 243 { 244 line++; 245 col = 0; 246 } 247 ++col; 248 } 249 250 std::cerr 251 << "==== File: " << expect_path 252 << ", Line: " << line 253 << ", Column: " << col 254 << std::endl; 255 std::cerr << "=============================================" << std::endl; 256 257 // Print output 258 std::cerr << output; 259 std::cerr << "=============================================" << std::endl; 260 std::cerr << "==== End" << std::endl; 261 std::cerr << "=============================================" << std::endl; 262 return false; 263 } 264 return true; 265 } 266 267 }}}} 268 269 #endif 270