/* 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();
}