Skip to content

Commit c962d06

Browse files
committed
Test for output file permissions error
1 parent 5d82f9b commit c962d06

File tree

3 files changed

+66
-0
lines changed

3 files changed

+66
-0
lines changed

make/tests

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ test/interface/multi_chain_test$(EXE): $(call depends_on_test_models,
7070
test/interface/optimization_output_test$(EXE): $(call depends_on_test_models, optimization_output simple_jacobian_model)
7171
test/interface/output_sig_figs_test$(EXE): $(call depends_on_test_models, proper_sig_figs)
7272
test/interface/pathfinder_test$(EXE): $(call depends_on_test_models, multi_normal_model eight_schools empty)
73+
test/interface/permissions_test$(EXE): $(call depends_on_test_models, test_model)
7374
test/interface/print_uninitialized_test$(EXE): $(call depends_on_test_models, print_uninitialized)
7475
test/interface/save_metric_json_test$(EXE): $(call depends_on_test_models, simplex_model multi_normal_model)
7576
test/interface/variational_output_test$(EXE): $(call depends_on_test_models, variational_output)
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <cmdstan/stansummary_helper.hpp>
2+
#include <stan/io/stan_csv_reader.hpp>
3+
#include <stan/services/error_codes.hpp>
4+
#include <test/utility.hpp>
5+
#include <gtest/gtest.h>
6+
7+
using cmdstan::test::convert_model_path;
8+
9+
TEST(interface, unwritable_file) {
10+
std::string model = convert_model_path(
11+
std::vector{"src", "test", "test-models", "test_model"});
12+
std::string output
13+
= convert_model_path(std::vector{"test", "output_unwritable.csv"});
14+
15+
cmdstan::test::temporary_unwritable_file guard(output);
16+
17+
std::string command
18+
= model + " sample num_warmup=1 num_samples=1 output file=" + output;
19+
20+
cmdstan::test::run_command_output out = cmdstan::test::run_command(command);
21+
EXPECT_IN_STRING("Permission denied", out.output);
22+
EXPECT_TRUE(out.hasError);
23+
}

src/test/utility.hpp

+42
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <string>
1313
#include <vector>
1414
#include <sys/stat.h>
15+
#include <sys/types.h>
1516

1617
#define EXPECT_IN_STRING(needle, haystack) \
1718
EXPECT_TRUE(boost::algorithm::contains(haystack, needle)) \
@@ -318,6 +319,47 @@ bool is_valid_JSON(std::string &text) {
318319
return !document.Parse<0>(text.c_str()).HasParseError();
319320
}
320321

322+
namespace internal {
323+
324+
// TODO: can be replaced with std::filesystem when we have better compiler
325+
// support for C++17 (currently missing from our minimum clang version)
326+
#ifdef _WIN32
327+
#include <io.h>
328+
void make_unwritable_file(const std::string &filename) {
329+
_chmod(filename.c_str(), _S_IREAD);
330+
}
331+
void make_writable(const std::string &filename) {
332+
_chmod(filename.c_str(), _S_IWRITE);
333+
}
334+
#else
335+
void make_unwritable(const std::string &filename) {
336+
chmod(filename.c_str(), 0444);
337+
}
338+
void make_writable(const std::string &filename) {
339+
chmod(filename.c_str(), 0644);
340+
}
341+
#endif
342+
343+
} // namespace internal
344+
345+
struct temporary_unwritable_file {
346+
std::string filename;
347+
temporary_unwritable_file(const std::string &filename) : filename(filename) {
348+
{
349+
// this will create the file if it does not exist
350+
std::ofstream ofs(filename);
351+
ofs.close();
352+
}
353+
EXPECT_TRUE(file_exists(filename));
354+
internal::make_unwritable(filename);
355+
}
356+
~temporary_unwritable_file() noexcept(false) {
357+
internal::make_writable(filename);
358+
EXPECT_EQ(remove(filename.c_str()), 0);
359+
EXPECT_FALSE(file_exists(filename));
360+
}
361+
};
362+
321363
} // namespace test
322364
} // namespace cmdstan
323365
#endif

0 commit comments

Comments
 (0)