| 1 | // (C) COPYRIGHT 2017 ARM Limited |
| 2 | // Based on gzip_test.cpp by: |
| 3 | // (C) Copyright 2008 CodeRage, LLC (turkanis at coderage dot com) |
| 4 | // (C) Copyright 2004-2007 Jonathan Turkanis |
| 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying |
| 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.) |
| 7 | |
| 8 | // See http://www.boost.org/libs/iostreams for documentation. |
| 9 | |
| 10 | // Note: basically a copy-paste of the gzip test |
| 11 | |
| 12 | #include <cstddef> |
| 13 | #include <string> |
| 14 | #include <boost/iostreams/copy.hpp> |
| 15 | #include <boost/iostreams/device/array.hpp> |
| 16 | #include <boost/iostreams/device/back_inserter.hpp> |
| 17 | #include <boost/iostreams/filter/lzma.hpp> |
| 18 | #include <boost/iostreams/filter/test.hpp> |
| 19 | #include <boost/iostreams/filtering_stream.hpp> |
| 20 | #include <boost/ref.hpp> |
| 21 | #include <boost/range/iterator_range.hpp> |
| 22 | #include <boost/test/test_tools.hpp> |
| 23 | #include <boost/test/unit_test.hpp> |
| 24 | #include "detail/sequence.hpp" |
| 25 | #include "detail/verification.hpp" |
| 26 | |
| 27 | using namespace boost; |
| 28 | using namespace boost::iostreams; |
| 29 | using namespace boost::iostreams::test; |
| 30 | namespace io = boost::iostreams; |
| 31 | using boost::unit_test::test_suite; |
| 32 | |
| 33 | template<class T> struct basic_test_alloc: std::allocator<T> |
| 34 | { |
| 35 | basic_test_alloc() |
| 36 | { |
| 37 | } |
| 38 | |
| 39 | basic_test_alloc( basic_test_alloc const& /*other*/ ) |
| 40 | { |
| 41 | } |
| 42 | |
| 43 | template<class U> |
| 44 | basic_test_alloc( basic_test_alloc<U> const & /*other*/ ) |
| 45 | { |
| 46 | } |
| 47 | |
| 48 | template<class U> struct rebind |
| 49 | { |
| 50 | typedef basic_test_alloc<U> other; |
| 51 | }; |
| 52 | }; |
| 53 | |
| 54 | typedef basic_test_alloc<char> lzma_alloc; |
| 55 | |
| 56 | void compression_test() |
| 57 | { |
| 58 | text_sequence data; |
| 59 | |
| 60 | // Test compression and decompression with custom allocator |
| 61 | BOOST_CHECK( |
| 62 | test_filter_pair( basic_lzma_compressor<lzma_alloc>(), |
| 63 | basic_lzma_decompressor<lzma_alloc>(), |
| 64 | std::string(data.begin(), data.end()) ) |
| 65 | ); |
| 66 | } |
| 67 | |
| 68 | void multiple_member_test() |
| 69 | { |
| 70 | text_sequence data; |
| 71 | std::vector<char> temp, dest; |
| 72 | |
| 73 | // Write compressed data to temp, twice in succession |
| 74 | filtering_ostream out; |
| 75 | out.push(t: lzma_compressor()); |
| 76 | out.push(t: io::back_inserter(cnt&: temp)); |
| 77 | io::copy(src: make_iterator_range(r&: data), snk&: out); |
| 78 | out.push(t: io::back_inserter(cnt&: temp)); |
| 79 | io::copy(src: make_iterator_range(r&: data), snk&: out); |
| 80 | |
| 81 | // Read compressed data from temp into dest |
| 82 | filtering_istream in; |
| 83 | in.push(t: lzma_decompressor()); |
| 84 | in.push(t: array_source(&temp[0], temp.size())); |
| 85 | io::copy(src&: in, snk: io::back_inserter(cnt&: dest)); |
| 86 | |
| 87 | // Check that dest consists of two copies of data |
| 88 | BOOST_REQUIRE_EQUAL(data.size() * 2, dest.size()); |
| 89 | BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin())); |
| 90 | BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin() + dest.size() / 2)); |
| 91 | |
| 92 | dest.clear(); |
| 93 | io::copy( |
| 94 | src: array_source(&temp[0], temp.size()), |
| 95 | snk: io::compose(filter: lzma_decompressor(), fod: io::back_inserter(cnt&: dest))); |
| 96 | |
| 97 | // Check that dest consists of two copies of data |
| 98 | BOOST_REQUIRE_EQUAL(data.size() * 2, dest.size()); |
| 99 | BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin())); |
| 100 | BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin() + dest.size() / 2)); |
| 101 | } |
| 102 | |
| 103 | void array_source_test() |
| 104 | { |
| 105 | std::string data = "simple test string." ; |
| 106 | std::string encoded; |
| 107 | |
| 108 | filtering_ostream out; |
| 109 | out.push(t: lzma_compressor()); |
| 110 | out.push(t: io::back_inserter(cnt&: encoded)); |
| 111 | io::copy(src: make_iterator_range(r&: data), snk&: out); |
| 112 | |
| 113 | std::string res; |
| 114 | io::array_source src(encoded.data(),encoded.length()); |
| 115 | io::copy(src: io::compose(filter: io::lzma_decompressor(), fod: src), snk: io::back_inserter(cnt&: res)); |
| 116 | |
| 117 | BOOST_CHECK_EQUAL(data, res); |
| 118 | } |
| 119 | |
| 120 | void empty_file_test() |
| 121 | { |
| 122 | // This test is in response to https://svn.boost.org/trac/boost/ticket/5237 |
| 123 | // The previous implementation of gzip_compressor only wrote the gzip file |
| 124 | // header when the first bytes of uncompressed input were processed, causing |
| 125 | // incorrect behavior for empty files |
| 126 | BOOST_CHECK( |
| 127 | test_filter_pair( lzma_compressor(), |
| 128 | lzma_decompressor(), |
| 129 | std::string() ) |
| 130 | ); |
| 131 | } |
| 132 | |
| 133 | void multipart_test() |
| 134 | { |
| 135 | // This test verifies that the lzma_decompressor properly handles a file |
| 136 | // that consists of multiple concatenated files (matches unxz behaviour) |
| 137 | static const char multipart_file[] = { |
| 138 | '\xfd', '\x37', '\x7a', '\x58', '\x5a', '\x00', '\x00', '\x04', '\xe6', '\xd6', '\xb4', '\x46', |
| 139 | '\x02', '\x00', '\x21', '\x01', '\x1c', '\x00', '\x00', '\x00', '\x10', '\xcf', '\x58', '\xcc', |
| 140 | '\xe0', '\x00', '\x14', '\x00', '\x11', '\x5d', '\x00', '\x26', '\x1a', '\x49', '\xc6', '\x67', |
| 141 | '\x41', '\x3f', '\x96', '\x8c', '\x25', '\x02', '\xb3', '\x4d', '\x16', '\xa8', '\xb4', '\x40', |
| 142 | '\x00', '\x00', '\x00', '\x00', '\xeb', '\xad', '\x3f', '\xbf', '\x8c', '\x8c', '\x72', '\x25', |
| 143 | '\x00', '\x01', '\x2d', '\x15', '\x2f', '\x0b', '\x71', '\x6d', '\x1f', '\xb6', '\xf3', '\x7d', |
| 144 | '\x01', '\x00', '\x00', '\x00', '\x00', '\x04', '\x59', '\x5a', '\xfd', '\x37', '\x7a', '\x58', |
| 145 | '\x5a', '\x00', '\x00', '\x04', '\xe6', '\xd6', '\xb4', '\x46', '\x02', '\x00', '\x21', '\x01', |
| 146 | '\x1c', '\x00', '\x00', '\x00', '\x10', '\xcf', '\x58', '\xcc', '\xe0', '\x00', '\x14', '\x00', |
| 147 | '\x11', '\x5d', '\x00', '\x26', '\x1a', '\x49', '\xc6', '\x67', '\x41', '\x4d', '\x84', '\x0c', |
| 148 | '\x25', '\x1f', '\x5e', '\x1d', '\x4a', '\x91', '\x61', '\xa0', '\x00', '\x00', '\x00', '\x00', |
| 149 | '\x56', '\x76', '\x71', '\xf0', '\x54', '\x21', '\xa2', '\x5b', '\x00', '\x01', '\x2d', '\x15', |
| 150 | '\x2f', '\x0b', '\x71', '\x6d', '\x1f', '\xb6', '\xf3', '\x7d', '\x01', '\x00', '\x00', '\x00', |
| 151 | '\x00', '\x04', '\x59', '\x5a', '\xfd', '\x37', '\x7a', '\x58', '\x5a', '\x00', '\x00', '\x04', |
| 152 | '\xe6', '\xd6', '\xb4', '\x46', '\x00', '\x00', '\x00', '\x00', '\x1c', '\xdf', '\x44', '\x21', |
| 153 | '\x1f', '\xb6', '\xf3', '\x7d', '\x01', '\x00', '\x00', '\x00', '\x00', '\x04', '\x59', '\x5a', |
| 154 | '\xfd', '\x37', '\x7a', '\x58', '\x5a', '\x00', '\x00', '\x04', '\xe6', '\xd6', '\xb4', '\x46', |
| 155 | '\x02', '\x00', '\x21', '\x01', '\x1c', '\x00', '\x00', '\x00', '\x10', '\xcf', '\x58', '\xcc', |
| 156 | '\xe0', '\x00', '\x14', '\x00', '\x11', '\x5d', '\x00', '\x26', '\x1a', '\x49', '\xc6', '\x67', |
| 157 | '\x41', '\x5b', '\x71', '\x8c', '\x25', '\x3c', '\x08', '\xec', '\x79', '\xa7', '\x7b', '\x60', |
| 158 | '\x00', '\x00', '\x00', '\x00', '\xc7', '\x62', '\xbb', '\xaa', '\x59', '\x96', '\x2b', '\xa4', |
| 159 | '\x00', '\x01', '\x2d', '\x15', '\x2f', '\x0b', '\x71', '\x6d', '\x1f', '\xb6', '\xf3', '\x7d', |
| 160 | '\x01', '\x00', '\x00', '\x00', '\x00', '\x04', '\x59', '\x5a' |
| 161 | }; |
| 162 | |
| 163 | filtering_istream in; |
| 164 | std::string line; |
| 165 | |
| 166 | in.push(t: lzma_decompressor()); |
| 167 | in.push(t: io::array_source(multipart_file, sizeof(multipart_file))); |
| 168 | |
| 169 | // First part |
| 170 | std::getline(is&: in, str&: line); |
| 171 | BOOST_CHECK_EQUAL("Line 1" , line); |
| 172 | std::getline(is&: in, str&: line); |
| 173 | BOOST_CHECK_EQUAL("Line 2" , line); |
| 174 | std::getline(is&: in, str&: line); |
| 175 | BOOST_CHECK_EQUAL("Line 3" , line); |
| 176 | |
| 177 | // Second part immediately follows |
| 178 | std::getline(is&: in, str&: line); |
| 179 | BOOST_CHECK_EQUAL("Line 4" , line); |
| 180 | std::getline(is&: in, str&: line); |
| 181 | BOOST_CHECK_EQUAL("Line 5" , line); |
| 182 | std::getline(is&: in, str&: line); |
| 183 | BOOST_CHECK_EQUAL("Line 6" , line); |
| 184 | |
| 185 | // Then an empty part, followed by one last 3-line part. |
| 186 | std::getline(is&: in, str&: line); |
| 187 | BOOST_CHECK_EQUAL("Line 7" , line); |
| 188 | std::getline(is&: in, str&: line); |
| 189 | BOOST_CHECK_EQUAL("Line 8" , line); |
| 190 | std::getline(is&: in, str&: line); |
| 191 | BOOST_CHECK_EQUAL("Line 9" , line); |
| 192 | |
| 193 | // Check for lzma errors too. |
| 194 | BOOST_CHECK(!in.bad()); |
| 195 | } |
| 196 | |
| 197 | void multithreaded_test() |
| 198 | { |
| 199 | text_sequence data; |
| 200 | |
| 201 | // Get correct compressed string at level 2. |
| 202 | // Tests legacy capability of providing a single integer to the |
| 203 | // lzma_compressor constructor to be used as the "level" to initialize |
| 204 | // lzma_params. |
| 205 | std::string correct_level_2; |
| 206 | { |
| 207 | filtering_ostream out; |
| 208 | out.push(t: lzma_compressor(2)); |
| 209 | out.push(t: io::back_inserter(cnt&: correct_level_2)); |
| 210 | io::copy(src: make_iterator_range(r&: data), snk&: out); |
| 211 | } |
| 212 | |
| 213 | // Tests omitting the threads parameters and arriving at same compressed data. |
| 214 | BOOST_CHECK( |
| 215 | test_output_filter( lzma_compressor(lzma_params(2)), |
| 216 | std::string(data.begin(), data.end()), |
| 217 | correct_level_2 ) |
| 218 | ); |
| 219 | |
| 220 | // Test specifying a single thread and arriving at same compressed data. |
| 221 | BOOST_CHECK( |
| 222 | test_output_filter( lzma_compressor(lzma_params(2, 1)), |
| 223 | std::string(data.begin(), data.end()), |
| 224 | correct_level_2 ) |
| 225 | ); |
| 226 | |
| 227 | // Test specifying multiple threads and arriving at same compressed data. |
| 228 | BOOST_CHECK( |
| 229 | test_output_filter( lzma_compressor(lzma_params(2, 4)), |
| 230 | std::string(data.begin(), data.end()), |
| 231 | correct_level_2 ) |
| 232 | ); |
| 233 | |
| 234 | // Test specifying "0" threads, which is interpreted as |
| 235 | // using all cores, or 1 thread if such capability is missing. |
| 236 | BOOST_CHECK( |
| 237 | test_output_filter( lzma_compressor(lzma_params(2, 0)), |
| 238 | std::string(data.begin(), data.end()), |
| 239 | correct_level_2 ) |
| 240 | ); |
| 241 | |
| 242 | // Test that decompressor works to decompress the output with various thread values. |
| 243 | // Threading shouldn't affect the decompression and, in fact, isn't |
| 244 | // threaded in current implementation of liblzma. Both the level and |
| 245 | // threads options are ignored by the decompressor. |
| 246 | BOOST_CHECK( |
| 247 | test_input_filter( lzma_decompressor(lzma_params(2, 1)), |
| 248 | correct_level_2, |
| 249 | std::string(data.begin(), data.end()) ) |
| 250 | ); |
| 251 | BOOST_CHECK( |
| 252 | test_input_filter( lzma_decompressor(lzma_params(2, 4)), |
| 253 | correct_level_2, |
| 254 | std::string(data.begin(), data.end()) ) |
| 255 | ); |
| 256 | BOOST_CHECK( |
| 257 | test_input_filter( lzma_decompressor(lzma_params(2, 0)), |
| 258 | correct_level_2, |
| 259 | std::string(data.begin(), data.end()) ) |
| 260 | ); |
| 261 | |
| 262 | } |
| 263 | |
| 264 | test_suite* init_unit_test_suite(int, char* []) |
| 265 | { |
| 266 | test_suite* test = BOOST_TEST_SUITE("lzma test" ); |
| 267 | test->add(BOOST_TEST_CASE(&compression_test)); |
| 268 | test->add(BOOST_TEST_CASE(&multiple_member_test)); |
| 269 | test->add(BOOST_TEST_CASE(&array_source_test)); |
| 270 | test->add(BOOST_TEST_CASE(&empty_file_test)); |
| 271 | test->add(BOOST_TEST_CASE(&multipart_test)); |
| 272 | test->add(BOOST_TEST_CASE(&multithreaded_test)); |
| 273 | return test; |
| 274 | } |
| 275 | |