The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
test_filesystem.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2015 - 2025
3 by Iris Morelle <shadowm2006@gmail.com>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16#include <boost/test/unit_test.hpp>
17
18#include "config_cache.hpp"
19#include "filesystem.hpp"
20#include "game_config.hpp"
21#include "log.hpp"
23
24#if 0
25namespace {
26
27template<typename T>
28void dump(const T& v)
29{
30 for(typename T::const_iterator k = v.begin(); k != v.end(); ++k) {
31 PLAIN_LOG << " * " << *k;
32 }
33}
34
35}
36#endif
37
38BOOST_AUTO_TEST_SUITE( filesystem ); // implicit namespace filesystem
39
40const std::string& gamedata = game_config::path;
41
42BOOST_AUTO_TEST_CASE( test_fs_game_path_reverse_engineering )
43{
44 const std::string maincfg = "_main.cfg";
45
46 std::string gamedata_rev = get_wml_location("_main.cfg").value();
47
48 const std::size_t strip_len = (maincfg + "/data/").length();
49 BOOST_REQUIRE(gamedata_rev.length() > strip_len);
50 gamedata_rev.resize(gamedata_rev.length() - strip_len);
51
52 BOOST_CHECK_EQUAL( gamedata_rev, gamedata );
53}
54
55BOOST_AUTO_TEST_CASE( test_fs_base )
56{
57 BOOST_CHECK( is_root("/") );
58 BOOST_CHECK( is_root("////") );
59 BOOST_CHECK( is_root("/../") );
60 BOOST_CHECK( is_root("/../.././") );
61 BOOST_CHECK( is_root("/.././../.") );
62 BOOST_CHECK( is_root("/.") );
63
64 BOOST_CHECK( is_relative(".") );
65 BOOST_CHECK( is_relative("..") );
66 BOOST_CHECK( is_relative("../foo") );
67 BOOST_CHECK( is_relative("foo") );
68 BOOST_CHECK( !is_relative("/./../foo/..") );
69 BOOST_CHECK( !is_relative("/foo/..") );
70 BOOST_CHECK( !is_relative("/foo") );
71 BOOST_CHECK( !is_relative("///foo") );
72
73 BOOST_CHECK( is_directory("/") );
74 BOOST_CHECK( is_directory("/.") );
75 BOOST_CHECK( is_directory("/./././.") );
76 BOOST_CHECK( is_directory("/..") );
77
78 BOOST_CHECK( is_directory(".") );
79 BOOST_CHECK( is_directory("..") );
80 BOOST_CHECK( is_directory("./././.") );
81
82 BOOST_CHECK( is_directory(gamedata + "/data/core/../../data/././../data/././core") );
83
84 BOOST_CHECK( file_exists("/") );
85 BOOST_CHECK( file_exists("/.") );
86 BOOST_CHECK( file_exists("/./././.") );
87 BOOST_CHECK( file_exists("/..") );
88
89 BOOST_CHECK_EQUAL( base_name("foo/bar/baz.cfg"), "baz.cfg" );
90 // FIXME: BFS gives "." for this, Unix basename gives "bar"!
91 //BOOST_CHECK_EQUAL( base_name("foo/bar/"), "bar" );
92 BOOST_CHECK_EQUAL( base_name("foo/bar"), "bar" );
93 BOOST_CHECK_EQUAL( base_name("/"), "/" );
94 BOOST_CHECK_EQUAL( base_name(""), "" );
95
96 BOOST_CHECK_EQUAL( directory_name("foo/bar/baz.cfg"), "foo/bar" );
97 BOOST_CHECK_EQUAL( directory_name("foo/bar/"), "foo/bar" );
98 BOOST_CHECK_EQUAL( directory_name("foo/bar"), "foo" );
99 BOOST_CHECK_EQUAL( directory_name("/"), "" );
100 BOOST_CHECK_EQUAL( directory_name(""), "" );
101
102 // TODO normalize_path
103
104 //BOOST_CHECK_EQUAL( normalize_path(gamedata + "/data/core/../../data/././../data/././core"),
105 // gamedata + "/data/core" );
106}
107
108BOOST_AUTO_TEST_CASE( test_fs_enum )
109{
110 const std::string path = "data/test/test/filesystem/enum";
111
112 const std::vector<std::string> expected_filenames {
113 "_initial.cfg",
114 "A1.cfg",
115 "A2.cfg",
116 "A3.cfg",
117 "B1.cfg",
118 "B2.cfg",
119 "B3.cfg",
120 "_final.cfg"};
121 const std::vector<std::string> expected_dirnames {
122 "D1",
123 "D2",
124 "D3"};
125
126 std::vector<std::string> files, dirs;
127 std::vector<std::string> expected_filepaths, expected_dirpaths;
128
129 for(const std::string& n : expected_filenames) {
130 expected_filepaths.push_back(gamedata + "/" + path + "/" + n);
131 }
132
133 for(const std::string& n : expected_dirnames) {
134 expected_dirpaths.push_back(gamedata + "/" + path + "/" + n);
135 }
136
137 // FIXME: get_files_in_dir with mode == FILE_NAME_ONLY will fail to reorder
138 // entries because the sorting code looks for forward slashes.
139 // This affects both the BFS-based and legacy implementations.
140 get_files_in_dir(path, &files, &dirs, name_mode::ENTIRE_FILE_PATH, filter_mode::NO_FILTER, reorder_mode::DO_REORDER);
141
142 BOOST_CHECK( files == expected_filepaths );
143 BOOST_CHECK( dirs == expected_dirpaths );
144}
145
146BOOST_AUTO_TEST_CASE( test_fs_binary_path )
147{
149
150 cache.clear_defines();
151 cache.add_define("EDITOR");
152 cache.add_define("MULTIPLAYER");
153
154 config main_config = cache.get_config(game_config::path + "/data");
155 game_config_view game_config_view_ = game_config_view::wrap(main_config);
156
157 const filesystem::binary_paths_manager bin_paths_manager(game_config_view_);
158
159 //load_language_list();
160 game_config::load_config(main_config.mandatory_child("game_config"));
161
162 BOOST_CHECK_EQUAL( get_binary_dir_location("images", ".").value(), gamedata + "/images/." );
163
164 BOOST_CHECK_EQUAL( get_binary_file_location("images", "wesnoth-icon.png").value(),
165 gamedata + "/data/core/images/wesnoth-icon.png" );
166
167 BOOST_CHECK_EQUAL( get_binary_file_location("music", "silence.ogg").value(),
168 gamedata + "/data/core/music/silence.ogg" );
169
170 BOOST_CHECK_EQUAL( get_binary_file_location("sounds", "explosion.ogg").value(),
171 gamedata + "/data/core/sounds/explosion.ogg" );
172
173 BOOST_CHECK_EQUAL( get_independent_binary_file_path("images", "wesnoth-icon.png").value(),
174 "data/core/images/wesnoth-icon.png" );
175
176 // Inexistent paths are resolved empty.
177 BOOST_CHECK( !get_binary_dir_location("images", "").has_value() );
178 BOOST_CHECK( !get_binary_dir_location("inexistent_resource_type", "").has_value() );
179 BOOST_CHECK( !get_binary_file_location("image", "wesnoth-icon.png").has_value() );
180 BOOST_CHECK( !get_binary_file_location("images", "bunnies_and_ponies_and_rainbows_oh_em_gee.psd").has_value() );
181 BOOST_CHECK( !get_binary_file_location("music", "this_track_does_not_exist.aiff").has_value() );
182 BOOST_CHECK( !get_binary_file_location("sounds", "rude_noises.aiff").has_value() );
183 BOOST_CHECK( !get_independent_binary_file_path("images", "dopefish.txt").has_value() );
184
185 // to_asset_path checks
186 std::string path = gamedata + "/data/core/images/wesnoth-icon.png";
187 utils::optional<std::string> outpath = to_asset_path(path, "", "images");
188 BOOST_CHECK( outpath.has_value() );
189 BOOST_CHECK_EQUAL( outpath.value(), "wesnoth-icon.png" );
190
191 path = gamedata + "/images/icons/action/modern/language_25-active.png";
192 outpath = to_asset_path(path, "", "images");
193 BOOST_CHECK( outpath.has_value() );
194 BOOST_CHECK_EQUAL( outpath.value(), "icons/action/modern/language_25-active.png" );
195
196 path = gamedata + "/data/core/sounds/ambient/campfire.ogg";
197 outpath = to_asset_path(path, "", "sounds");
198 BOOST_CHECK( outpath.has_value() );
199 BOOST_CHECK_EQUAL( outpath.value(), "ambient/campfire.ogg" );
200
201 path = gamedata + "/images/this/path/doesn't/exist/campfire.ogg";
202 BOOST_CHECK( !to_asset_path(path, "", "images").has_value() );
203}
204
205BOOST_AUTO_TEST_CASE( test_fs_wml_path )
206{
207 const std::string& userdata = get_user_data_dir();
208
209 BOOST_CHECK_EQUAL( get_wml_location("").value_or(""), "" );
210
211 BOOST_CHECK_EQUAL( get_wml_location("_main.cfg").value_or(""), gamedata + "/data/_main.cfg" );
212 BOOST_CHECK_EQUAL( get_wml_location("core/_main.cfg").value_or(""), gamedata + "/data/core/_main.cfg" );
213 BOOST_CHECK_EQUAL( get_wml_location(".", std::string("")).value_or(""), "." );
214
215 BOOST_CHECK_EQUAL( get_wml_location("~/").value_or(""), userdata + "/data/" );
216
217 // Inexistent paths are resolved empty.
218 BOOST_CHECK( !get_wml_location("why_would_anyone_ever_name_a_file_like_this").has_value() );
219}
220
221BOOST_AUTO_TEST_CASE( test_fs_search )
222{
223 const std::string& userdata = get_user_data_dir();
224
225 BOOST_CHECK_EQUAL( nearest_extant_parent(userdata + "/THIS_DOES_NOT_EXIST/foo/bar"), userdata );
226
227 BOOST_CHECK_EQUAL( nearest_extant_parent(gamedata + "/THIS_DOES_NOT_EXIST_EITHER/foo"), gamedata );
228 BOOST_CHECK_EQUAL( nearest_extant_parent(gamedata + "/data/_main.cfg"), gamedata + "/data" );
229 BOOST_CHECK_EQUAL( nearest_extant_parent(gamedata + "/data/core/THIS_DOES_NOT_EXIST/test"), gamedata + "/data/core" );
230
231 BOOST_CHECK_EQUAL( nearest_extant_parent("/THIS_HOPEFULLY_DOES_NOT_EXIST"), "/" );
232 BOOST_CHECK_EQUAL( nearest_extant_parent("/THIS_HOPEFULLY_DOES_NOT_EXIST/foo/bar"), "/" );
233 BOOST_CHECK_EQUAL( nearest_extant_parent("/THIS_HOPEFULLY_DOES_NOT_EXIST/foo/bar/.."), "/" );
234}
235
236BOOST_AUTO_TEST_CASE( test_fs_fluff )
237{
238 BOOST_CHECK( looks_like_pbl("foo.pbl") );
239 BOOST_CHECK( looks_like_pbl("FOO.PBL") );
240 BOOST_CHECK( looks_like_pbl("Foo.Pbl") );
241 BOOST_CHECK( !looks_like_pbl("foo.pbl.cfg") );
242
243 BOOST_CHECK( is_gzip_file("foo.gz") );
244 BOOST_CHECK( !is_gzip_file("foo.gz.bz2") );
245 BOOST_CHECK( is_bzip2_file("foo.bz2") );
246 BOOST_CHECK( !is_bzip2_file("foo.bz2.gz") );
247
248 BOOST_CHECK( is_compressed_file("foo.gz") );
249 BOOST_CHECK( is_compressed_file("foo.bz2") );
250 BOOST_CHECK( !is_compressed_file("foo.txt") );
251
252 // FIXME: Is this even intended?
253 BOOST_CHECK( !is_gzip_file("foo.GZ") );
254 BOOST_CHECK( !is_bzip2_file("foo.BZ2") );
255 BOOST_CHECK( !is_compressed_file("foo.GZ") );
256 BOOST_CHECK( !is_compressed_file("foo.BZ2") );
257}
258
259BOOST_AUTO_TEST_CASE( test_blacklist_pattern_list )
260{
261 // Basic wildcard matching
262 blacklist_pattern_list blacklist({"*.exe", "potato*", "rutabaga"}, {});
263 BOOST_CHECK(blacklist.match_file("program.exe"));
264 BOOST_CHECK(blacklist.match_file("rutabaga"));
265 BOOST_CHECK(blacklist.match_file("potatoes"));
266 BOOST_CHECK(!blacklist.match_file("notapotato"));
267
268 // ? should match a single character
269 blacklist_pattern_list qmark({"file?.txt"}, {});
270 BOOST_CHECK(qmark.match_file("file1.txt"));
271 BOOST_CHECK(qmark.match_file("fileA.txt"));
272 BOOST_CHECK(!qmark.match_file("file12.txt"));
273
274 // Leading/trailing wildcards
275 blacklist_pattern_list leadtrail({"*secret*"}, {});
276 BOOST_CHECK(leadtrail.match_file("my_secret_file"));
277 BOOST_CHECK(leadtrail.match_file("secret"));
278 BOOST_CHECK(!leadtrail.match_file("public"));
279
280 // Pattern that should match everything
281 blacklist_pattern_list all({"*"}, {});
282 BOOST_CHECK(all.match_file("anything"));
283 BOOST_CHECK(all.match_file(""));
284
285 // Directory pattern matching (match_dir should behave the same)
286 blacklist_pattern_list dirpat({}, {"temp*", "__MACOSX"});
287 BOOST_CHECK(dirpat.match_dir("temp"));
288 BOOST_CHECK(dirpat.match_dir("temp123"));
289 BOOST_CHECK(dirpat.match_dir("__MACOSX"));
290 BOOST_CHECK(dirpat.match_dir("templates"));
291
292 // Matching is case-sensitive, though it probably shouldn't be on Windows
293 blacklist_pattern_list cs({"Readme.TXT"}, {});
294 BOOST_CHECK(cs.match_file("Readme.TXT"));
295 BOOST_CHECK(!cs.match_file("readme.txt"));
296
297 // Patterns with dots and special chars
298 blacklist_pattern_list specials({"*.tar.gz", "backup?.old", "*.cfg"}, {});
299 BOOST_CHECK(specials.match_file("archive.tar.gz"));
300 BOOST_CHECK(specials.match_file("backup1.old"));
301 BOOST_CHECK(!specials.match_file("backup10.old"));
302 BOOST_CHECK(specials.match_file("foo.cfg"));
303
304 // Sanity: nonexistent patterns should not match
305 blacklist_pattern_list none({}, {});
306 BOOST_CHECK(!none.match_file("something.exe"));
307 BOOST_CHECK(!none.match_dir("temp"));
308}
309
310
311BOOST_AUTO_TEST_SUITE_END()
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition config.cpp:362
Singleton class to manage game config file caching.
static config_cache & instance()
Get reference to the singleton object.
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static game_config_view wrap(const config &cfg)
Declarations for File-IO.
Standard logging facilities (interface).
#define PLAIN_LOG
Definition log.hpp:296
std::string path
void load_config(const config &v)
std::unordered_map< std::string, cache_item > cache
Definition sound.cpp:146
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
The paths manager is responsible for recording the various paths that binary files may be located at.
BOOST_AUTO_TEST_SUITE(filesystem)
const std::string & gamedata
BOOST_AUTO_TEST_CASE(test_fs_game_path_reverse_engineering)
static map_location::direction n