diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index 7e4bf730..1f30ac2d 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -26,6 +26,7 @@ local debug = [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ] ; for local v in NO_COMPRESSION NO_ZLIB NO_BZIP2 + NO_LZMA { $(v) = [ modules.peek : $(v) ] ; } @@ -60,6 +61,21 @@ else } } +if $(NO_COMPRESSION) != 1 && $(NO_LZMA) != 1 +{ + using lzma : : boost_lzma @tag : : true ; + lzma-requirements = + [ ac.check-library /lzma//lzma : /lzma//lzma + lzma.cpp ] ; +} +else +{ + if $(debug) + { + ECHO "notice: iostreams: not using lzma compression " ; + } +} + local sources = file_descriptor.cpp mapped_file.cpp ; lib boost_iostreams @@ -68,6 +84,7 @@ lib boost_iostreams BOOST_IOSTREAMS_USE_DEPRECATED $(zlib-requirements) $(bzip2-requirements) + $(lzma-requirements) : : shared:BOOST_IOSTREAMS_DYN_LINK=1 ; diff --git a/doc/bibliography.html b/doc/bibliography.html index babe8899..284df7bb 100644 --- a/doc/bibliography.html +++ b/doc/bibliography.html @@ -102,6 +102,10 @@

Bibliography

Seward, J. The Libbzip2 Compression Library. See http://www.bzip.org. + [Collin] + Collin, L. XZ Utils. See https://tukaani.org/xz/. + + [Sutter] Sutter, H. More Exceptional C++, 40 New Engineering Puzzles, Programming Problems, and Solutions. C++ In-Depth Series, B. Stroustrup, ed. Addison-Wesley, 2002. @@ -133,4 +137,4 @@

Bibliography

- \ No newline at end of file + diff --git a/doc/installation.html b/doc/installation.html index ddc73fb0..7406dcef 100644 --- a/doc/installation.html +++ b/doc/installation.html @@ -30,8 +30,11 @@

Installation

href="classes/regex_filter.html">regular expression filters depend on Boost.Regex, and the compressions filters rely on the third-party libraries zlib ([Gailly]) and - libbz2 ([Seward]). + name="gailly" href="bibliography.html#gailly">[Gailly]), + libbz2 ([Seward]) + and liblzma ([Collin]). + Note that liblzma refers to the version from xz-utils which is the version available in + for example Linux distributions, not the liblzma from the LZMA SDK from 7zip. To obatin zlib and libbz2, see the instructions here and here.

The components which are implemented in .cpp or which rely @@ -85,6 +88,11 @@

Installation

zlib.cpp zlib + + boost/iostreams/filter/lzma.hpp + lzma.cpp + liblzma + @@ -93,18 +101,19 @@

Building with Boost.Build

run b2 from the directory libs/iostreams/build, or from the Boost root directory.

If you want to use the compression filters when building iostreams, you - have two choices. You can setup the zlib and/or bzip2 toolsets in Boost + have two choices. You can setup the zlib, bzip2 and/or LZMA toolsets in Boost Build in a jamfile, preferably user-config.jam, as documented in Boost.Build. - Alternatively you can let iostreams setup the zlib and/or bzip2 toolsets + Note that building from source is not supported for LZMA. + Alternatively you can let iostreams setup the zlib, bzip2 and/or LZMA toolsets for you using default values. The former is preferred, especially if your zlib and/or bzip2 installations cannot be found by the iostreams setup.

-

On most UNIX systems, it should not be necessary to setup the zlib - and/or bzip2 toolsets since the zlib and libbz2 headers and binaries are - already installed in locations where they will be found automatically. On - Windows the zlib and/or bzip2 binaries need to be in the PATH, else they +

On most UNIX systems, it should not be necessary to setup the zlib, + bzip2 and/or lzma toolsets since the zlib, libbz2 and liblzma headers and binaries are + already installed in locations where they will be found automatically. On + Windows the zlib, bzip2 and/or LZMA binaries need to be in the PATH, else they will not ordinarily be found by default, so it is always a good idea under - Windows to setup the zlib and/or bzip2 toolsets in your own jamfile.

+ Windows to setup the zlib, bzip2 and/or LZMA toolsets in your own jamfile.

You can turn off compression filters when building iostreams by passing one of the Boost Build variables in the table below, defined to 1, using the -s @@ -127,6 +136,10 @@

Building with Boost.Build

NO_ZLIB Disable support for the zlib filters. + + NO_LZMA + Disable support for the LZMA/xz filters. + diff --git a/include/boost/iostreams/filter/lzma.hpp b/include/boost/iostreams/filter/lzma.hpp new file mode 100644 index 00000000..a4e7d7ff --- /dev/null +++ b/include/boost/iostreams/filter/lzma.hpp @@ -0,0 +1,363 @@ +// (C) Copyright Milan Svoboda 2008. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.) + +// See http://www.boost.org/libs/iostreams for documentation. + +// Note: custom allocators are not supported on VC6, since that compiler +// had trouble finding the function lzma_base::do_init. + +#ifndef BOOST_IOSTREAMS_LZMA_HPP_INCLUDED +#define BOOST_IOSTREAMS_LZMA_HPP_INCLUDED + +#if defined(_MSC_VER) +# pragma once +#endif + +#include +#include // streamsize. +#include // allocator, bad_alloc. +#include +#include // MSVC, STATIC_CONSTANT, DEDUCED_TYPENAME, DINKUM. +#include +#include // buffer size. +#include +#include +#include +#include // failure, streamsize. +#include +#include +#include + +// Must come last. +#ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable:4251 4231 4660) // Dependencies not exported. +#endif +#include + +namespace boost { namespace iostreams { + +namespace lzma { + +typedef void* (*alloc_func)(void*, size_t, size_t); +typedef void (*free_func)(void*, void*); + + // Compression levels + +BOOST_IOSTREAMS_DECL extern const uint32_t no_compression; +BOOST_IOSTREAMS_DECL extern const uint32_t best_speed; +BOOST_IOSTREAMS_DECL extern const uint32_t best_compression; +BOOST_IOSTREAMS_DECL extern const uint32_t default_compression; + + // Status codes + +BOOST_IOSTREAMS_DECL extern const int okay; +BOOST_IOSTREAMS_DECL extern const int stream_end; +BOOST_IOSTREAMS_DECL extern const int unsupported_check; +BOOST_IOSTREAMS_DECL extern const int mem_error; +BOOST_IOSTREAMS_DECL extern const int options_error; +BOOST_IOSTREAMS_DECL extern const int data_error; +BOOST_IOSTREAMS_DECL extern const int buf_error; +BOOST_IOSTREAMS_DECL extern const int prog_error; + + // Flush codes + +BOOST_IOSTREAMS_DECL extern const int finish; +BOOST_IOSTREAMS_DECL extern const int full_flush; +BOOST_IOSTREAMS_DECL extern const int sync_flush; +BOOST_IOSTREAMS_DECL extern const int run; + + // Code for current OS + + // Null pointer constant. + +const int null = 0; + + // Default values + +} // End namespace lzma. + +// +// Class name: lzma_params. +// Description: Encapsulates the parameters passed to lzmadec_init +// to customize compression and decompression. +// +struct lzma_params { + + // Non-explicit constructor. + lzma_params( uint32_t level = lzma::default_compression ) + : level(level) + { } + uint32_t level; +}; + +// +// Class name: lzma_error. +// Description: Subclass of std::ios::failure thrown to indicate +// lzma errors other than out-of-memory conditions. +// +class BOOST_IOSTREAMS_DECL lzma_error : public BOOST_IOSTREAMS_FAILURE { +public: + explicit lzma_error(int error); + int error() const { return error_; } + static void check BOOST_PREVENT_MACRO_SUBSTITUTION(int error); +private: + int error_; +}; + +namespace detail { + +template +struct lzma_allocator_traits { +#ifndef BOOST_NO_STD_ALLOCATOR + typedef typename Alloc::template rebind::other type; +#else + typedef std::allocator type; +#endif +}; + +template< typename Alloc, + typename Base = // VC6 workaround (C2516) + BOOST_DEDUCED_TYPENAME lzma_allocator_traits::type > +struct lzma_allocator : private Base { +private: + typedef typename Base::size_type size_type; +public: + BOOST_STATIC_CONSTANT(bool, custom = + (!is_same, Base>::value)); + typedef typename lzma_allocator_traits::type allocator_type; + static void* allocate(void* self, size_t items, size_t size); + static void deallocate(void* self, void* address); +}; + +class BOOST_IOSTREAMS_DECL lzma_base { +public: + typedef char char_type; +protected: + lzma_base(); + ~lzma_base(); + void* stream() { return stream_; } + template + void init( const lzma_params& p, + bool compress, + lzma_allocator& zalloc ) + { + bool custom = lzma_allocator::custom; + do_init( p, compress, + custom ? lzma_allocator::allocate : 0, + custom ? lzma_allocator::deallocate : 0, + &zalloc ); + } + void before( const char*& src_begin, const char* src_end, + char*& dest_begin, char* dest_end ); + void after( const char*& src_begin, char*& dest_begin, + bool compress ); + int deflate(int action); + int inflate(int action); + void reset(bool compress, bool realloc); +private: + void do_init( const lzma_params& p, bool compress, + lzma::alloc_func, + lzma::free_func, + void* derived ); + void* stream_; // Actual type: lzmadec_stream*. + uint32_t level; +}; + +// +// Template name: lzma_compressor_impl +// Description: Model of C-Style Filter implementing compression by +// delegating to the lzma function deflate. +// +template > +class lzma_compressor_impl : public lzma_base, public lzma_allocator { +public: + lzma_compressor_impl(const lzma_params& = lzma::default_compression); + ~lzma_compressor_impl(); + bool filter( const char*& src_begin, const char* src_end, + char*& dest_begin, char* dest_end, bool flush ); + void close(); +}; + +// +// Template name: lzma_compressor_impl +// Description: Model of C-Style Filte implementing decompression by +// delegating to the lzma function inflate. +// +template > +class lzma_decompressor_impl : public lzma_base, public lzma_allocator { +public: + lzma_decompressor_impl(const lzma_params&); + lzma_decompressor_impl(); + ~lzma_decompressor_impl(); + bool filter( const char*& begin_in, const char* end_in, + char*& begin_out, char* end_out, bool flush ); + void close(); +}; + +} // End namespace detail. + +// +// Template name: lzma_compressor +// Description: Model of InputFilter and OutputFilter implementing +// compression using lzma. +// +template > +struct basic_lzma_compressor + : symmetric_filter, Alloc> +{ +private: + typedef detail::lzma_compressor_impl impl_type; + typedef symmetric_filter base_type; +public: + typedef typename base_type::char_type char_type; + typedef typename base_type::category category; + basic_lzma_compressor( const lzma_params& = lzma::default_compression, + std::streamsize buffer_size = default_device_buffer_size ); +}; +BOOST_IOSTREAMS_PIPABLE(basic_lzma_compressor, 1) + +typedef basic_lzma_compressor<> lzma_compressor; + +// +// Template name: lzma_decompressor +// Description: Model of InputFilter and OutputFilter implementing +// decompression using lzma. +// +template > +struct basic_lzma_decompressor + : symmetric_filter, Alloc> +{ +private: + typedef detail::lzma_decompressor_impl impl_type; + typedef symmetric_filter base_type; +public: + typedef typename base_type::char_type char_type; + typedef typename base_type::category category; + basic_lzma_decompressor( std::streamsize buffer_size = default_device_buffer_size ); + basic_lzma_decompressor( const lzma_params& p, + std::streamsize buffer_size = default_device_buffer_size ); +}; +BOOST_IOSTREAMS_PIPABLE(basic_lzma_decompressor, 1) + +typedef basic_lzma_decompressor<> lzma_decompressor; + +//----------------------------------------------------------------------------// + +//------------------Implementation of lzma_allocator--------------------------// + +namespace detail { + +template +void* lzma_allocator::allocate + (void* self, size_t items, size_t size) +{ + size_type len = items * size; + char* ptr = + static_cast(self)->allocate + (len + sizeof(size_type) + #if BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1) + , (char*)0 + #endif + ); + *reinterpret_cast(ptr) = len; + return ptr + sizeof(size_type); +} + +template +void lzma_allocator::deallocate(void* self, void* address) +{ + char* ptr = reinterpret_cast(address) - sizeof(size_type); + size_type len = *reinterpret_cast(ptr) + sizeof(size_type); + static_cast(self)->deallocate(ptr, len); +} + +//------------------Implementation of lzma_compressor_impl--------------------// + +template +lzma_compressor_impl::lzma_compressor_impl(const lzma_params& p) +{ init(p, true, static_cast&>(*this)); } + +template +lzma_compressor_impl::~lzma_compressor_impl() +{ reset(true, false); } + +template +bool lzma_compressor_impl::filter + ( const char*& src_begin, const char* src_end, + char*& dest_begin, char* dest_end, bool flush ) +{ + before(src_begin, src_end, dest_begin, dest_end); + int result = deflate(flush ? lzma::finish : lzma::run); + after(src_begin, dest_begin, true); + lzma_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(result); + return result != lzma::stream_end; +} + +template +void lzma_compressor_impl::close() { reset(true, true); } + +//------------------Implementation of lzma_decompressor_impl------------------// + +template +lzma_decompressor_impl::lzma_decompressor_impl(const lzma_params& p) +{ init(p, false, static_cast&>(*this)); } + +template +lzma_decompressor_impl::~lzma_decompressor_impl() +{ reset(false, false); } + +template +lzma_decompressor_impl::lzma_decompressor_impl() +{ + lzma_params p; + init(p, false, static_cast&>(*this)); +} + +template +bool lzma_decompressor_impl::filter + ( const char*& src_begin, const char* src_end, + char*& dest_begin, char* dest_end, bool flush ) +{ + before(src_begin, src_end, dest_begin, dest_end); + int result = inflate(flush ? lzma::finish : lzma::run); + after(src_begin, dest_begin, false); + lzma_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(result); + return result != lzma::stream_end; +} + +template +void lzma_decompressor_impl::close() { reset(false, true); } + +} // End namespace detail. + +//------------------Implementation of lzma_compressor-----------------------// + +template +basic_lzma_compressor::basic_lzma_compressor + (const lzma_params& p, std::streamsize buffer_size) + : base_type(buffer_size, p) { } + +//------------------Implementation of lzma_decompressor-----------------------// + +template +basic_lzma_decompressor::basic_lzma_decompressor + (std::streamsize buffer_size) + : base_type(buffer_size) { } + +template +basic_lzma_decompressor::basic_lzma_decompressor + (const lzma_params& p, std::streamsize buffer_size) + : base_type(buffer_size, p) { } + +//----------------------------------------------------------------------------// + +} } // End namespaces iostreams, boost. + +#include // Pops abi_suffix.hpp pragmas. +#ifdef BOOST_MSVC +# pragma warning(pop) +#endif + +#endif // #ifndef BOOST_IOSTREAMS_LZMA_HPP_INCLUDED diff --git a/src/lzma.cpp b/src/lzma.cpp new file mode 100644 index 00000000..ea5f3da2 --- /dev/null +++ b/src/lzma.cpp @@ -0,0 +1,144 @@ +// (C) Copyright Jonathan Turkanis 2003. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.) + +// See http://www.boost.org/libs/iostreams for documentation. + +// Define BOOST_IOSTREAMS_SOURCE so that +// knows that we are building the library (possibly exporting code), rather +// than using it (possibly importing code). +#define BOOST_IOSTREAMS_SOURCE + +#include + +#include +#include +#include + +namespace boost { namespace iostreams { + +namespace lzma { + + // Compression levels + +const uint32_t no_compression = 0; +const uint32_t best_speed = 1; +const uint32_t best_compression = 9; +const uint32_t default_compression = 2; + + // Status codes + +const int okay = LZMA_OK; +const int stream_end = LZMA_STREAM_END; +const int unsupported_check = LZMA_UNSUPPORTED_CHECK; +const int mem_error = LZMA_MEM_ERROR; +const int options_error = LZMA_OPTIONS_ERROR; +const int data_error = LZMA_DATA_ERROR; +const int buf_error = LZMA_BUF_ERROR; +const int prog_error = LZMA_PROG_ERROR; + + // Flush codes + +const int finish = LZMA_FINISH; +const int full_flush = LZMA_FULL_FLUSH; +const int sync_flush = LZMA_SYNC_FLUSH; +const int run = LZMA_RUN; + + // Code for current OS + +} // End namespace lzma. + +//------------------Implementation of lzma_error------------------------------// + +lzma_error::lzma_error(int error) + : BOOST_IOSTREAMS_FAILURE("lzma error"), error_(error) + { } + +void lzma_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(int error) +{ + switch (error) { + case LZMA_OK: + case LZMA_STREAM_END: + return; + case LZMA_MEM_ERROR: + boost::throw_exception(std::bad_alloc()); + default: + boost::throw_exception(lzma_error(error)); + } +} + +//------------------Implementation of lzma_base-------------------------------// + +namespace detail { + +lzma_base::lzma_base() + : stream_(new lzma_stream) + { } + +lzma_base::~lzma_base() { delete static_cast(stream_); } + +void lzma_base::before( const char*& src_begin, const char* src_end, + char*& dest_begin, char* dest_end ) +{ + lzma_stream* s = static_cast(stream_); + s->next_in = reinterpret_cast(const_cast(src_begin)); + s->avail_in = static_cast(src_end - src_begin); + s->next_out = reinterpret_cast(dest_begin); + s->avail_out= static_cast(dest_end - dest_begin); +} + +void lzma_base::after(const char*& src_begin, char*& dest_begin, bool) +{ + lzma_stream* s = static_cast(stream_); + src_begin = const_cast(reinterpret_cast(s->next_in)); + dest_begin = reinterpret_cast(s->next_out); +} + +int lzma_base::deflate(int action) +{ + return lzma_code(static_cast(stream_), static_cast(action)); +} + +int lzma_base::inflate(int action) +{ + return lzma_code(static_cast(stream_), static_cast(action)); +} + +void lzma_base::reset(bool compress, bool realloc) +{ + lzma_stream* s = static_cast(stream_); + lzma_end(s); + if (realloc) + { + memset(s, 0, sizeof(*s)); + + lzma_error::check BOOST_PREVENT_MACRO_SUBSTITUTION( + compress ? + lzma_easy_encoder(s, level, LZMA_CHECK_CRC32) : + lzma_stream_decoder(s, 100 * 1024 * 1024, LZMA_CONCATENATED) + ); + } +} + +void lzma_base::do_init + ( const lzma_params& p, bool compress, + lzma::alloc_func, lzma::free_func, + void* ) +{ + lzma_stream* s = static_cast(stream_); + + memset(s, 0, sizeof(*s)); + + level = p.level; + lzma_error::check BOOST_PREVENT_MACRO_SUBSTITUTION( + compress ? + lzma_easy_encoder(s, p.level, LZMA_CHECK_CRC32) : + lzma_stream_decoder(s, 100 * 1024 * 1024, LZMA_CONCATENATED) + ); +} + +} // End namespace detail. + +//----------------------------------------------------------------------------// + +} } // End namespaces iostreams, boost. diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 1745def6..75740bc7 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -9,9 +9,11 @@ import stlport ; import modules ; +import ac ; local NO_BZIP2 = [ modules.peek : NO_BZIP2 ] ; local NO_ZLIB = [ modules.peek : NO_ZLIB ] ; +local NO_LZMA = [ modules.peek : NO_LZMA ] ; local LARGE_FILE_TEMP = [ modules.peek : LARGE_FILE_TEMP ] ; local LARGE_FILE_KEEP = [ modules.peek : LARGE_FILE_KEEP ] ; @@ -148,6 +150,13 @@ rule compile-fail-iostreams ( sources * : requirements * : target-name ? ) { [ test-iostreams zlib_test.cpp ../build//boost_iostreams ] ; } + if ! $(NO_LZMA) + { + using lzma : : boost_lzma @tag : : true ; + all-tests += [ test-iostreams + lzma_test.cpp ../build//boost_iostreams : + [ ac.check-library /lzma//lzma : : no ] ] ; + } test-suite "iostreams" : $(all-tests) ; diff --git a/test/lzma_test.cpp b/test/lzma_test.cpp new file mode 100644 index 00000000..63ac83da --- /dev/null +++ b/test/lzma_test.cpp @@ -0,0 +1,189 @@ +// (C) COPYRIGHT 2017 ARM Limited +// (C) Copyright 2008 CodeRage, LLC (turkanis at coderage dot com) +// (C) Copyright 2004-2007 Jonathan Turkanis +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.) + +// See http://www.boost.org/libs/iostreams for documentation. + +// Note: basically a copy-paste of the gzip test + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "detail/sequence.hpp" +#include "detail/verification.hpp" + +using namespace boost; +using namespace boost::iostreams; +using namespace boost::iostreams::test; +namespace io = boost::iostreams; +using boost::unit_test::test_suite; + +struct lzma_alloc : std::allocator { + lzma_alloc() { } + lzma_alloc(const lzma_alloc& other) { } + template + lzma_alloc(const std::allocator& other) { } +}; + +void compression_test() +{ + text_sequence data; + + // Test compression and decompression with custom allocator + BOOST_CHECK( + test_filter_pair( basic_lzma_compressor(), + basic_lzma_decompressor(), + std::string(data.begin(), data.end()) ) + ); +} + +void multiple_member_test() +{ + text_sequence data; + std::vector temp, dest; + + // Write compressed data to temp, twice in succession + filtering_ostream out; + out.push(lzma_compressor()); + out.push(io::back_inserter(temp)); + io::copy(make_iterator_range(data), out); + out.push(io::back_inserter(temp)); + io::copy(make_iterator_range(data), out); + + // Read compressed data from temp into dest + filtering_istream in; + in.push(lzma_decompressor()); + in.push(array_source(&temp[0], temp.size())); + io::copy(in, io::back_inserter(dest)); + + // Check that dest consists of two copies of data + BOOST_REQUIRE_EQUAL(data.size() * 2, dest.size()); + BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin())); + BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin() + dest.size() / 2)); + + dest.clear(); + io::copy( + array_source(&temp[0], temp.size()), + io::compose(lzma_decompressor(), io::back_inserter(dest))); + + // Check that dest consists of two copies of data + BOOST_REQUIRE_EQUAL(data.size() * 2, dest.size()); + BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin())); + BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin() + dest.size() / 2)); +} + +void array_source_test() +{ + std::string data = "simple test string."; + std::string encoded; + + filtering_ostream out; + out.push(lzma_compressor()); + out.push(io::back_inserter(encoded)); + io::copy(make_iterator_range(data), out); + + std::string res; + io::array_source src(encoded.data(),encoded.length()); + io::copy(io::compose(io::lzma_decompressor(), src), io::back_inserter(res)); + + BOOST_CHECK_EQUAL(data, res); +} + +void empty_file_test() +{ + // This test is in response to https://svn.boost.org/trac/boost/ticket/5237 + // The previous implementation of gzip_compressor only wrote the gzip file + // header when the first bytes of uncompressed input were processed, causing + // incorrect behavior for empty files + BOOST_CHECK( + test_filter_pair( lzma_compressor(), + lzma_decompressor(), + std::string() ) + ); +} + +void multipart_test() +{ + // This test verifies that the lzma_decompressor properly handles a file + // that consists of multiple concatenated files (matches unxz behaviour) + static const char multipart_file[] = { + '\xfd', '\x37', '\x7a', '\x58', '\x5a', '\x00', '\x00', '\x04', '\xe6', '\xd6', '\xb4', '\x46', + '\x02', '\x00', '\x21', '\x01', '\x1c', '\x00', '\x00', '\x00', '\x10', '\xcf', '\x58', '\xcc', + '\xe0', '\x00', '\x14', '\x00', '\x11', '\x5d', '\x00', '\x26', '\x1a', '\x49', '\xc6', '\x67', + '\x41', '\x3f', '\x96', '\x8c', '\x25', '\x02', '\xb3', '\x4d', '\x16', '\xa8', '\xb4', '\x40', + '\x00', '\x00', '\x00', '\x00', '\xeb', '\xad', '\x3f', '\xbf', '\x8c', '\x8c', '\x72', '\x25', + '\x00', '\x01', '\x2d', '\x15', '\x2f', '\x0b', '\x71', '\x6d', '\x1f', '\xb6', '\xf3', '\x7d', + '\x01', '\x00', '\x00', '\x00', '\x00', '\x04', '\x59', '\x5a', '\xfd', '\x37', '\x7a', '\x58', + '\x5a', '\x00', '\x00', '\x04', '\xe6', '\xd6', '\xb4', '\x46', '\x02', '\x00', '\x21', '\x01', + '\x1c', '\x00', '\x00', '\x00', '\x10', '\xcf', '\x58', '\xcc', '\xe0', '\x00', '\x14', '\x00', + '\x11', '\x5d', '\x00', '\x26', '\x1a', '\x49', '\xc6', '\x67', '\x41', '\x4d', '\x84', '\x0c', + '\x25', '\x1f', '\x5e', '\x1d', '\x4a', '\x91', '\x61', '\xa0', '\x00', '\x00', '\x00', '\x00', + '\x56', '\x76', '\x71', '\xf0', '\x54', '\x21', '\xa2', '\x5b', '\x00', '\x01', '\x2d', '\x15', + '\x2f', '\x0b', '\x71', '\x6d', '\x1f', '\xb6', '\xf3', '\x7d', '\x01', '\x00', '\x00', '\x00', + '\x00', '\x04', '\x59', '\x5a', '\xfd', '\x37', '\x7a', '\x58', '\x5a', '\x00', '\x00', '\x04', + '\xe6', '\xd6', '\xb4', '\x46', '\x00', '\x00', '\x00', '\x00', '\x1c', '\xdf', '\x44', '\x21', + '\x1f', '\xb6', '\xf3', '\x7d', '\x01', '\x00', '\x00', '\x00', '\x00', '\x04', '\x59', '\x5a', + '\xfd', '\x37', '\x7a', '\x58', '\x5a', '\x00', '\x00', '\x04', '\xe6', '\xd6', '\xb4', '\x46', + '\x02', '\x00', '\x21', '\x01', '\x1c', '\x00', '\x00', '\x00', '\x10', '\xcf', '\x58', '\xcc', + '\xe0', '\x00', '\x14', '\x00', '\x11', '\x5d', '\x00', '\x26', '\x1a', '\x49', '\xc6', '\x67', + '\x41', '\x5b', '\x71', '\x8c', '\x25', '\x3c', '\x08', '\xec', '\x79', '\xa7', '\x7b', '\x60', + '\x00', '\x00', '\x00', '\x00', '\xc7', '\x62', '\xbb', '\xaa', '\x59', '\x96', '\x2b', '\xa4', + '\x00', '\x01', '\x2d', '\x15', '\x2f', '\x0b', '\x71', '\x6d', '\x1f', '\xb6', '\xf3', '\x7d', + '\x01', '\x00', '\x00', '\x00', '\x00', '\x04', '\x59', '\x5a' + }; + + filtering_istream in; + std::string line; + + in.push(lzma_decompressor()); + in.push(io::array_source(multipart_file, sizeof(multipart_file))); + + // First part + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 1", line); + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 2", line); + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 3", line); + + // Second part immediately follows + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 4", line); + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 5", line); + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 6", line); + + // Then an empty part, followed by one last 3-line part. + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 7", line); + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 8", line); + std::getline(in, line); + BOOST_CHECK_EQUAL("Line 9", line); + + // Check for lzma errors too. + BOOST_CHECK(!in.bad()); +} + +test_suite* init_unit_test_suite(int, char* []) +{ + test_suite* test = BOOST_TEST_SUITE("lzma test"); + test->add(BOOST_TEST_CASE(&compression_test)); + test->add(BOOST_TEST_CASE(&multiple_member_test)); + test->add(BOOST_TEST_CASE(&array_source_test)); + test->add(BOOST_TEST_CASE(&empty_file_test)); + test->add(BOOST_TEST_CASE(&multipart_test)); + return test; +}