/* Copyright (C) 2019 Mike Fleetwood * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, see . */ /* Test ext2 * * Test the derived FileSystem interface classes which call the file system specific * executables. Rather than mocking command execution and returned output just run real * commands, effectively making this integration testing. * * Test case setup determines the file system supported actions using * get_filesystem_support() and individual tests are skipped if a feature is not * supported, just as GParted does for it's actions. * * Each test creates it's own sparse image file and a fresh file system, performs a test * on one FileSystem interface call and deletes the image file. This makes each test * independent and allows them to run as a non-root user, provided the file system command * itself doesn't require root. Errors reported for a failed interface call will include * the GParted OperationDetails, which in turn includes the file system specific command * used and stdout and stderr from it's execution. */ #include "GParted_Core.h" #include "FileSystem.h" #include "OperationDetail.h" #include "Partition.h" #include "Utils.h" #include "ext2.h" #include "gtest/gtest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace GParted { // Hacky XML parser which strips italic and bold markup added in // OperationDetail::set_description() and reverts just these 5 characters &<>'" encoded by // Glib::Markup::escape_text() -> g_markup_escape_text() -> append_escaped_text(). Glib::ustring strip_markup(const Glib::ustring& str) { size_t len = str.length(); size_t i = 0; Glib::ustring ret; ret.reserve(len); while (i < len) { if (str.compare(i, 3, "") == 0) i += 3; else if (str.compare(i, 4, "") == 0) i += 4; else if (str.compare(i, 3, "") == 0) i += 3; else if (str.compare(i, 4, "") == 0) i += 4; else if (str.compare(i, 5, "&") == 0) { ret.push_back('&'); i += 5; } else if (str.compare(i, 4, "<") == 0) { ret.push_back('<'); i += 4; } else if (str.compare(i, 4, ">") == 0) { ret.push_back('>'); i += 4; } else if (str.compare(i, 6, "'") == 0) { ret.push_back('\''); i += 6; } else if (str.compare(i, 6, """) == 0) { ret.push_back('"'); i += 6; } else { ret.push_back(str[i]); i++; } } return ret; } // Print method for OperationDetailStatus. std::ostream& operator<<(std::ostream& out, const OperationDetailStatus od_status) { switch (od_status) { case STATUS_NONE: out << "NONE"; break; case STATUS_EXECUTE: out << "EXECUTE"; break; case STATUS_SUCCESS: out << "SUCCESS"; break; case STATUS_ERROR: out << "ERROR"; break; case STATUS_INFO: out << "INFO"; break; case STATUS_WARNING: out << "WARNING"; break; default: break; } return out; } // Print method for an OperationDetail object. std::ostream& operator<<(std::ostream& out, const OperationDetail& od) { out << strip_markup(od.get_description()); Glib::ustring elapsed = od.get_elapsed_time(); if (! elapsed.empty()) out << " " << elapsed; if (od.get_status() != STATUS_NONE) out << " (" << od.get_status() << ")"; out << "\n"; for (unsigned int i = 0; i < od.get_childs().size(); i++) { out << *od.get_childs()[i]; } return out; } // Google Test 1.8.1 (and earlier) doesn't implement run-time test skipping so implement // our own for GParted run-time detected of unsupported file system features. // Ref: // Skipping tests at runtime with GTEST_SKIP() #1544 // https://github.com/google/googletest/pull/1544 // (Merged after Google Test 1.8.1) #define SKIP_IF_FS_DOESNT_SUPPORT(opt) \ if (s_ext2_support.opt != FS::EXTERNAL) \ { \ std::cout << __FILE__ << ":" << __LINE__ << ": Skip test. " \ << "Not supported or support not found" << std::endl; \ return; \ } class ext2Test : public ::testing::Test { protected: // Initialise top-level operation detail object with description ... ext2Test() : m_operation_detail("Operation details:", STATUS_NONE) {}; virtual void extra_setup(); virtual void TearDown(); static void SetUpTestCase(); static void TearDownTestCase(); static FileSystem* s_ext2_obj; static FS s_ext2_support; static const char* s_image_name; Partition m_partition; OperationDetail m_operation_detail; }; FileSystem* ext2Test::s_ext2_obj = NULL; FS ext2Test::s_ext2_support; const char* ext2Test::s_image_name = "test_ext2.img"; void ext2Test::extra_setup() { const Byte_Value ImageSize = 256*MEBIBYTE; // Create new 256M image file to work with. unlink(s_image_name); int fd = open(s_image_name, O_WRONLY|O_CREAT|O_NONBLOCK, 0666); ASSERT_GE(fd, 0) << "Failed to create image file '" << s_image_name << "'. errno=" << errno << "," << strerror(errno); ASSERT_EQ(ftruncate(fd, (off_t)ImageSize), 0) << "Failed to set image file '" << s_image_name << "' to size " << ImageSize << ". errno=" << errno << "," << strerror(errno); close(fd); // Use libparted to get the sector size etc. of the image file. PedDevice* lp_device = ped_device_get(s_image_name); ASSERT_TRUE(lp_device != NULL); // Prepare partition object spanning whole of the image file. m_partition.set_unpartitioned(s_image_name, lp_device->path, FS_EXT2, lp_device->length, lp_device->sector_size, false); ped_device_destroy(lp_device); lp_device = NULL; } void ext2Test::TearDown() { unlink(s_image_name); } // Common test case initialisation creating ext2 interface object and querying supported // operations. void ext2Test::SetUpTestCase() { s_ext2_obj = new ext2(FS_EXT2); s_ext2_support = s_ext2_obj->get_filesystem_support(); } // Common test case teardown destroying the ext2 interface object. void ext2Test::TearDownTestCase() { delete s_ext2_obj; s_ext2_obj = NULL; } TEST_F(ext2Test, Create) { SKIP_IF_FS_DOESNT_SUPPORT(create); extra_setup(); // Call create, check for success and print operation details on failure. ASSERT_TRUE(s_ext2_obj->create(m_partition, m_operation_detail)) << m_operation_detail; } } // namespace GParted // Re-execute current executable using xvfb-run so that it provides a virtual X11 display. void exec_using_xvfb_run(int argc, char** argv) { // argc+2 = Space for "xvfb-run" command, existing argc strings plus NULL pointer. size_t size = sizeof(char*) * (argc+2); char** new_argv = (char**)malloc(size); if (new_argv == NULL) { fprintf(stderr, "Failed to allocate %lu bytes of memory. errno=%d,%s\n", (unsigned long)size, errno, strerror(errno)); exit(EXIT_FAILURE); } new_argv[0] = strdup("xvfb-run"); if (new_argv[0] == NULL) { fprintf(stderr, "Failed to allocate %lu bytes of memory. errno=%d,%s\n", (unsigned long)strlen(new_argv[0])+1, errno, strerror(errno)); exit(EXIT_FAILURE); } // Copy argv pointers including final NULL pointer. for (unsigned int i = 0; i <= (unsigned)argc; i++) new_argv[i+1] = argv[i]; execvp(new_argv[0], new_argv); fprintf(stderr, "Failed to execute '%s %s ...'. errno=%d,%s\n", new_argv[0], new_argv[1], errno, strerror(errno)); exit(EXIT_FAILURE); } // Custom Google Test main(). // Reference: // * Google Test, Primer, Writing the main() function // https://github.com/google/googletest/blob/master/googletest/docs/primer.md#writing-the-main-function int main(int argc, char** argv) { printf("Running main() from %s\n", __FILE__); const char* display = getenv("DISPLAY"); if (display == NULL) { printf("DISPLAY environment variable unset. Executing 'xvfb-run %s ...'\n", argv[0]); exec_using_xvfb_run(argc, argv); } printf("DISPLAY=\"%s\"\n", display); testing::InitGoogleTest(&argc, argv); // Initialise threading in GParted to allow FileSystem interface classes to // successfully use Utils:: and Filesystem::execute_command(). GParted::GParted_Core::mainthread = Glib::Thread::self(); Gtk::Main gtk_main = Gtk::Main(); return RUN_ALL_TESTS(); }