KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_local_history_autosave_walk.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23// Regression test for a hang when a board is opened from a directory that also
24// contains a symlink escaping the project tree (e.g. a Wine prefix's
25// dosdevices/z: -> /). LOCAL_HISTORY::CollectAutosaveFilePairs walks the
26// project directory looking for stale autosave files and must terminate on
27// symlink cycles and refuse to follow links out of the scan root.
28
29#include <filesystem>
30#include <fstream>
31#include <system_error>
32
34#include <boost/test/unit_test.hpp>
35
36#include <local_history.h>
38
39#include <wx/filename.h>
40#include <wx/utils.h>
41
42
43namespace
44{
45
46// RAII temp tree. Cleanup on destruction matters here because the trees
47// contain symlinks back into themselves.
48struct ScratchRoot
49{
50 std::filesystem::path path;
51
52 explicit ScratchRoot( const char* aTag )
53 {
54 namespace fs = std::filesystem;
55 std::error_code ec;
56 path = fs::temp_directory_path( ec );
57 BOOST_REQUIRE_MESSAGE( !ec, ec.message() );
58
59 path /= std::string( "kicad_qa_autosave_walk_" ) + aTag + "_"
60 + std::to_string( static_cast<unsigned long>( wxGetProcessId() ) );
61
62 fs::remove_all( path, ec );
63 fs::create_directories( path, ec );
64 BOOST_REQUIRE_MESSAGE( !ec, ec.message() );
65 }
66
67 ~ScratchRoot()
68 {
69 std::error_code ec;
70 std::filesystem::remove_all( path, ec );
71 }
72};
73
74
75bool createSymlinkOrSkip( const std::filesystem::path& aTarget, const std::filesystem::path& aLink )
76{
77 std::error_code ec;
78 std::filesystem::create_directory_symlink( aTarget, aLink, ec );
79
80 if( ec )
81 BOOST_TEST_MESSAGE( "Skipping: symlink creation unsupported (" << ec.message() << ")" );
82
83 return !ec;
84}
85
86
87bool anyEndsWith( const std::vector<std::pair<wxString, wxString>>& aPairs, const wxString& aSuffix )
88{
89 for( const auto& [autosave, source] : aPairs )
90 {
91 if( autosave.EndsWith( aSuffix ) )
92 return true;
93 }
94
95 return false;
96}
97
98} // namespace
99
100
101BOOST_AUTO_TEST_SUITE( LocalHistoryAutosaveWalk )
102
103
104// A self-referencing directory symlink under the scan root must not send the
105// autosave walk into unbounded recursion; the real autosave file alongside it
106// must still be found.
107BOOST_AUTO_TEST_CASE( SelfReferencingSymlinkTerminates )
108{
109 namespace fs = std::filesystem;
110
111 const ScratchRoot scratch( "cycle" );
112 const wxString root = wxString::FromUTF8( scratch.path.string() );
113
114 std::ofstream( scratch.path / "_autosave-board.kicad_pcb" ) << "x";
115
116 if( !createSymlinkOrSkip( ".", scratch.path / "loop" ) )
117 return;
118
119 std::vector<std::pair<wxString, wxString>> pairs =
121
122 BOOST_CHECK( anyEndsWith( pairs, wxS( "_autosave-board.kicad_pcb" ) ) );
123}
124
125
126// A symlink whose target escapes the scan root -- the shape of a Wine prefix's
127// dosdevices/z: -> / -- must not be followed, otherwise the walk enumerates the
128// whole filesystem and never returns. Files living beyond the link must not
129// appear in the results.
130BOOST_AUTO_TEST_CASE( RootEscapeSymlinkIgnored )
131{
132 namespace fs = std::filesystem;
133
134 const ScratchRoot scratch( "escape" );
135 const ScratchRoot outside( "escape_outside" );
136
137 std::ofstream( scratch.path / "_autosave-inside.kicad_pcb" ) << "x";
138 std::ofstream( outside.path / "_autosave-outside.kicad_pcb" ) << "x";
139
140 const fs::path dosdevices = scratch.path / "wineprefix" / "dosdevices";
141 std::error_code ec;
142 fs::create_directories( dosdevices, ec );
143 BOOST_REQUIRE_MESSAGE( !ec, ec.message() );
144
145 if( !createSymlinkOrSkip( outside.path, dosdevices / "z:" ) )
146 return;
147
148 const wxString root = wxString::FromUTF8( scratch.path.string() );
149
150 std::vector<std::pair<wxString, wxString>> pairs =
152
153 BOOST_CHECK( anyEndsWith( pairs, wxS( "_autosave-inside.kicad_pcb" ) ) );
154 BOOST_CHECK( !anyEndsWith( pairs, wxS( "_autosave-outside.kicad_pcb" ) ) );
155}
156
157
static std::vector< std::pair< wxString, wxString > > CollectAutosaveFilePairs(const wxString &aAutosaveRoot, const wxString &aProjectPath, BACKUP_LOCATION aLocation)
Enumerate every (autosave, source) pair found under aAutosaveRoot for the project at aProjectPath,...
@ PROJECT_DIR
Inside the project directory (default)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(SelfReferencingSymlinkTerminates)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")