Add tests
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Armin Friedl 2020-02-25 22:01:31 +01:00
parent 6a2aa756ca
commit 9cb65ce856
Signed by: armin
GPG key ID: 48C726EEE7FBCBC8
15 changed files with 171 additions and 43 deletions

View file

@ -6,8 +6,14 @@ steps:
- name: build - name: build
image: arminfriedl/xwim-build image: arminfriedl/xwim-build
commands: commands:
- meson wrap install gtest
- meson build - meson build
- ninja -C build - ninja -C build
- ninja -C build test && ninja -C build coverage
- echo "******** TEST LOGS ***********"
- cat build/meson-logs/testlog.txt
- echo "****** COVERAGE LOGS *********"
- cat build/meson-logs/coverage.txt
trigger: trigger:
event: event:
@ -23,6 +29,7 @@ steps:
- name: build - name: build
image: arminfriedl/xwim-build image: arminfriedl/xwim-build
commands: commands:
- meson wrap install gtest
- meson --buildtype=release build - meson --buildtype=release build
- ninja -C build - ninja -C build
- mkdir xwim-${DRONE_TAG}-x86_64-glibc-linux - mkdir xwim-${DRONE_TAG}-x86_64-glibc-linux

View file

@ -790,8 +790,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched. # Note: If this tag is empty the current directory is searched.
INPUT = "@PROJECT_SRCDIR@" \ INPUT = "@PROJECT_SRCDIR@"
"@PROJECT_BINDIR@"
# This tag can be used to specify the character encoding of the source files # This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -864,7 +863,7 @@ EXCLUDE_SYMBOLS =
# that contain example code fragments that are included (see the \include # that contain example code fragments that are included (see the \include
# command). # command).
EXAMPLE_PATH = "@PROJECT_EXAMPLESDIR@" EXAMPLE_PATH =
# If the value of the EXAMPLE_PATH tag contains directories, you can use the # If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
@ -2020,8 +2019,7 @@ SEARCH_INCLUDES = YES
# preprocessor. # preprocessor.
# This tag requires that the tag SEARCH_INCLUDES is set to YES. # This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH = "@PROJECT_SRCDIR@" \ INCLUDE_PATH = "@PROJECT_SRCDIR@"
"@PROJECT_BINDIR@"
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the # patterns (like *.h and *.hpp) to filter out the header-files in the

View file

@ -14,7 +14,7 @@ else
endif endif
cdata.set('PROJECT_NAME', meson.project_name()) cdata.set('PROJECT_NAME', meson.project_name())
cdata.set('PROJECT_SRCDIR', join_paths(meson.current_source_dir(),'..','src',meson.project_name())) cdata.set('PROJECT_SRCDIR', join_paths(meson.current_source_dir(),'..','src'))
cdata.set('PROJECT_DOCDIR', meson.current_source_dir()) cdata.set('PROJECT_DOCDIR', meson.current_source_dir())
cdata.set('PROJECT_TESTDIR', join_paths(meson.current_source_dir(),'..','test')) cdata.set('PROJECT_TESTDIR', join_paths(meson.current_source_dir(),'..','test'))

View file

@ -1,5 +1,9 @@
project('xwim', 'cpp', project('xwim', 'cpp',
version: '0.2', version: '0.2',
default_options: ['cpp_std=c++17']) default_options: ['cpp_std=c++17',
'warning_level=3',
'b_coverage=true'])
subdir('src') subdir('src')
subdir('doc')
subdir('test')

View file

@ -12,6 +12,7 @@
namespace xwim { namespace xwim {
/** Class for interacting with archives */
class Archive { class Archive {
private: private:
std::filesystem::path path; std::filesystem::path path;
@ -19,7 +20,13 @@ class Archive {
public: public:
explicit Archive(std::filesystem::path path); explicit Archive(std::filesystem::path path);
/** Generate an ArchiveSpec by analysing the archive at `path`
*
* @returns ArchiveSpec for the archive
*/
ArchiveSpec check(); ArchiveSpec check();
/** Extract the archive at `path` according to given ExtractSpec */
void extract(ExtractSpec extract_spec); void extract(ExtractSpec extract_spec);
}; };

View file

@ -4,9 +4,16 @@
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <fmt/format.h>
namespace xwim { namespace xwim {
/** A view into an archive entry
*
* The view is non-owning and the caller must guarantee
* that the parent archive entry is valid when the view
* is accessed.
*/
class ArchiveEntryView { class ArchiveEntryView {
private: private:
archive_entry* ae; archive_entry* ae;

View file

@ -1,8 +1,17 @@
/** @file fileformats.hpp
* @brief Handle archive extensions
*/
#pragma once #pragma once
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
namespace logger = spdlog; namespace logger = spdlog;
#include <filesystem>
#include <set>
#include <string>
namespace xwim {
/** Common archive formats understood by xwim /** Common archive formats understood by xwim
* *
* The underlying libarchive backend retrieves format information by a process * The underlying libarchive backend retrieves format information by a process
@ -11,21 +20,20 @@ namespace logger = spdlog;
* Stripping extensions via `std::filesystem::path` does not work reliably since * Stripping extensions via `std::filesystem::path` does not work reliably since
* it gets easily confused by dots in the regular file name. * it gets easily confused by dots in the regular file name.
*/ */
const std::set<std::string> fileformats{".7z", ".7zip", ".jar", ".tgz",
".bz2", ".bzip2", ".gz", ".gzip",
".rar", ".tar", "xz", ".zip"};
#include <filesystem> /** Strip archive extensions from a path
#include <set> *
#include <string> * @returns Base filename without archive extensions
*/
namespace xwim { inline std::filesystem::path stem(const std::filesystem::path& path) {
const std::set<std::string> fileformats{
".7z", ".7zip", ".jar", ".tgz", ".bz2", ".bzip2", ".gz",
".gzip", ".rar", ".tar", ".tar.gz", ".tar.bz2", ".tar.xz", ".zip"};
inline std::filesystem::path stem(std::filesystem::path& path) {
std::filesystem::path p_stem{path}; std::filesystem::path p_stem{path};
logger::trace("Stemming {}", p_stem.string()); logger::trace("Stemming {}", p_stem.string());
p_stem = p_stem.filename();
while (fileformats.find(p_stem.extension().string()) != fileformats.end()) { while (fileformats.find(p_stem.extension().string()) != fileformats.end()) {
p_stem = p_stem.stem(); p_stem = p_stem.stem();
logger::trace("Stemmed to {}", p_stem.string()); logger::trace("Stemmed to {}", p_stem.string());

View file

@ -1,14 +1,14 @@
src = ['main.cpp', xwim_src = ['main.cpp',
'archive.cpp', 'archive.cpp',
'archive_sys.cpp'] 'archive_sys.cpp']
inc = ['archive.hpp', xwim_inc = ['archive.hpp',
'spec.hpp', 'spec.hpp',
'archive_sys.hpp', 'archive_sys.hpp',
'fileformats.hpp'] 'fileformats.hpp']
libs = [dependency('libarchive', required: true), xwim_libs = [dependency('libarchive', required: true),
dependency('fmt', required: true, static: true), dependency('fmt', required: true),
dependency('spdlog', required: true, static: true)] dependency('spdlog', required: true)]
executable('xwim', src, inc, dependencies: libs) executable('xwim', xwim_src, xwim_inc, dependencies: xwim_libs)

View file

@ -1,35 +1,57 @@
#pragma once #pragma once
#include <archive.h> #include <archive.h>
#include <fmt/core.h> #include <fmt/format.h>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
namespace xwim { namespace xwim {
/** Properties of an archive
*
* These properties can be retrieved by analyzing the
* archive. There is no outside-knowledge. All information
* is in the archive.
*/
struct ArchiveSpec { struct ArchiveSpec {
bool has_single_root = false; bool has_single_root = false; /** There is only a single file xor a single
bool is_root_filename = false; folder at the archive's root */
bool is_root_dir = false; bool is_root_filename = false; /** the name of the (single) root is the same
bool has_subarchive = false; as the stemmed archive file name. Cannot be
true if `has_single_root` is false */
bool is_root_dir = false; /** The (single) root is a folder. Cnnot be true if
`has_single_root` is false */
bool has_subarchive = false; /** Whether the archive contains sub-archives */
}; };
/** Properties influencing the extraction process
*
* These properties can be set to influence the extraction
* process accordingly.
*/
struct ExtractSpec { struct ExtractSpec {
bool make_dir = false; bool make_dir = false; /** Create a new directory for extraction at `dirname` */
std::filesystem::path dirname{}; std::filesystem::path dirname{}; /** The path to a directory for extraction */
bool extract_subarchive = false; bool extract_subarchive = false; /** Recursively extract sub-archives */
}; };
} // namespace xwim } // namespace xwim
#if FMT_VERSION < 50300
typedef fmt::basic_parse_context<char> format_parse_context;
#endif
template <> template <>
struct fmt::formatter<xwim::ArchiveSpec> { struct fmt::formatter<xwim::ArchiveSpec> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } constexpr auto parse(format_parse_context & ctx) {
return ctx.begin();
}
template <typename FormatContext> template <typename FormatContext>
auto format(const xwim::ArchiveSpec& spec, FormatContext& ctx) { auto format(const xwim::ArchiveSpec& spec, FormatContext& ctx) {
return format_to(ctx.out(),"Archive[" return format_to(ctx.out(),
"Archive["
" .has_single_root={}," " .has_single_root={},"
" .is_root_filename={}" " .is_root_filename={}"
" .is_root_dir={}" " .is_root_dir={}"
@ -46,11 +68,13 @@ struct fmt::formatter<xwim::ExtractSpec> {
template <typename FormatContext> template <typename FormatContext>
auto format(const xwim::ExtractSpec& spec, FormatContext& ctx) { auto format(const xwim::ExtractSpec& spec, FormatContext& ctx) {
return format_to(ctx.out(), "Extract[" return format_to(ctx.out(),
"Extract["
" .make_dir={}," " .make_dir={},"
" .dirname={}" " .dirname={}"
" .extract_subarchive={}" " .extract_subarchive={}"
" ]", " ]",
spec.make_dir, spec.dirname.string(), spec.extract_subarchive); spec.make_dir, spec.dirname.string(),
spec.extract_subarchive);
} }
}; };

4
subprojects/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

11
test/archive_test.cpp Normal file
View file

@ -0,0 +1,11 @@
#include <gtest/gtest.h>
#include <archive.hpp>
#include <spec.hpp>
TEST(ArchiveTest, ArchiveSpecDetectsSingleRoot) {
xwim::Archive archive("test/archives/root.tar.gz");
xwim::ArchiveSpec spec = archive.check();
ASSERT_TRUE(spec.has_single_root);
}

View file

@ -0,0 +1 @@
configure_file(input: 'root.tar.gz', output: 'root.tar.gz', copy: true)

BIN
test/archives/root.tar.gz Normal file

Binary file not shown.

34
test/fileformats_test.cpp Normal file
View file

@ -0,0 +1,34 @@
#include <gtest/gtest.h>
#include <fileformats.hpp>
#include <string>
TEST(FileformatsTest, StemStripsSingleKnownExtension) {
std::filesystem::path archive_path {"/some/path/to/file.rar"};
ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"file"});
}
TEST(FileformatsTest, StemStripsMultipleKnownExtensions) {
std::filesystem::path archive_path{"/some/path/to/file.tar.rar.gz.7z.rar"};
ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"file"});
}
TEST(FileformatsTest, StemStripsOnlyKnownExtension) {
std::filesystem::path archive_path{"/some/path/to/file.ukn.rar"};
ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"file.ukn"});
}
TEST(FileformatsTest, StemStripsNothingWithoutKnownExtension) {
std::filesystem::path archive_path{"/some/path/to/file.ukn"};
ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"file.ukn"});
}
TEST(FileformatsTest, StemStripsNothingWithoutExtension) {
std::filesystem::path archive_path{"/some/path/to/filerar"};
ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"filerar"});
}

23
test/meson.build Normal file
View file

@ -0,0 +1,23 @@
# Run tests+coverage with `ninja clean && ninja test && ninja coverage`
gtest_proj = subproject('gtest')
gtest_dep = gtest_proj.get_variable('gtest_main_dep')
xwim_src = ['../src/archive.cpp',
'../src/archive_sys.cpp']
subdir('archives')
archive_test_exe = executable('archive_test_exe',
sources: ['archive_test.cpp', xwim_src],
include_directories: ['../src'],
dependencies: [gtest_dep, xwim_libs])
test('archive test', archive_test_exe)
fileformats_test_exe = executable('fileformats_test_exe',
sources: ['fileformats_test.cpp', xwim_src],
include_directories: ['../src'],
dependencies: [gtest_dep, xwim_libs])
test('fileformats test', fileformats_test_exe)