From c57e950ec7d5345b39789653b13857ad3cb99ab6 Mon Sep 17 00:00:00 2001 From: Dodji Seketeli Date: Tue, 27 Aug 2013 15:18:59 +0200 Subject: [PATCH] Initial writing/reading of an ABI corpus to an archive * configure.ac: Support detection of libzip dependency. Define new DEPS_CFLAGS and DEPS_LIBS variables for use in Makefile.am to refer to the dependency headers and libraries. * doc/website/mainpage.txt: Update this to talk about the new libzip dependency. * include/Makefile.am: Add abg-libzip-utils.h to the build system. * include/abg-corpus.h (corps): Hide abigail::corpus's private behind a pimpl idiom. (corpus::{drop_translation_units, get_file_path, set_file_path, write, read}): New methods. * include/abg-libxml-utils.h (new_reader_from_buffer): Declare new function. * include/abg-libzip-utils.h: New file. * src/Makefile.am: Add abg-corpus.cc and abg-libzip-utils.cc to the build system. Refer to the library and headers dependencies via the new DEPS_LIBS and DEPS_CFLAGS variables. * src/abg-corpus.cc: New file. * src/abg-ir.cc (translation::set_path): New method. * src/abg-libxml-utils.cc (new_reader_from_buffer): Define new function. * src/abg-libzip-utils.cc: New file. * src/abg-reader.cc (translation_unit::read): New overload. * src/abg-writer.cc: Inject the names from the std namespace into the abigail namespace, rather than into abigail::writer. (abigail::translation_unit::write): New overload. This can now use ofstream and the other stuff from std that are injected in the abigail:: namespace. * tests/Makefile.am: Add tests/test-write-read-archive.cc to the build system; use that to build runtestwritereadarchive. Also add the input test data from tests/data/test-write-read-archive/test[0-4].xml. * /tests/data/test-write-read-archive/test[0-4].xml: New test input data files. * tests/test-write-read-archive.cc: New test for this archive write/read support. Signed-off-by: Dodji Seketeli --- configure.ac | 14 + doc/website/mainpage.txt | 4 +- include/Makefile.am | 1 + include/abg-corpus.h | 20 +- include/abg-libxml-utils.h | 1 + include/abg-libzip-utils.h | 72 +++++ src/Makefile.am | 6 +- src/abg-corpus.cc | 320 +++++++++++++++++++ src/abg-ir.cc | 10 + src/abg-libxml-utils.cc | 15 + src/abg-libzip-utils.cc | 73 +++++ src/abg-reader.cc | 13 + src/abg-writer.cc | 53 ++- tests/Makefile.am | 16 +- tests/data/test-write-read-archive/test0.xml | 3 + tests/data/test-write-read-archive/test1.xml | 5 + tests/data/test-write-read-archive/test2.xml | 14 + tests/data/test-write-read-archive/test3.xml | 6 + tests/data/test-write-read-archive/test4.xml | 4 + tests/test-write-read-archive.cc | 176 ++++++++++ 20 files changed, 810 insertions(+), 16 deletions(-) create mode 100644 include/abg-libzip-utils.h create mode 100644 src/abg-corpus.cc create mode 100644 src/abg-libzip-utils.cc create mode 100644 tests/data/test-write-read-archive/test0.xml create mode 100644 tests/data/test-write-read-archive/test1.xml create mode 100644 tests/data/test-write-read-archive/test2.xml create mode 100644 tests/data/test-write-read-archive/test3.xml create mode 100644 tests/data/test-write-read-archive/test4.xml create mode 100644 tests/test-write-read-archive.cc diff --git a/configure.ac b/configure.ac index 9353446f..89aee174 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,20 @@ AC_SUBST(LIBXML2_VERSION) AC_SUBST(XML_LIBS) AC_SUBST(XML_CFLAGS) +dnl Check for dependency: libzip +LIBZIP_VERSION=0.10 +PKG_CHECK_MODULES(LIBZIP, libzip >= $LIBZIP_VERSION) + +AC_SUBST(LIBZIP_VERSION) +AC_SUBST(LIBZIP_LIBS) +AC_SUBST(LIBZIP_CFLAGS) + +DEPS_CPPFLAGS="$XML_CFLAGS $LIBZIP_CFLAGS" +AC_SUBST(DEPS_CPPFLAGS) + +DEPS_LIBS="$XML_LIBS $LIBZIP_LIBS" +AC_SUBST(DEPS_LIBS) + if test x$ABIGAIL_DEVEL != x; then DEVEL_CFLAGS="-g -Wall -Wextra -Werror" DEVEL_CXXFLAGS="-g -Wall -Wextra -Werror" diff --git a/doc/website/mainpage.txt b/doc/website/mainpage.txt index 32c2b00a..625ae206 100644 --- a/doc/website/mainpage.txt +++ b/doc/website/mainpage.txt @@ -11,7 +11,7 @@ constructions like types, variables, functions and declarations of a given library or program. For a given program or library, this set of constructions is called an ABI corpus. -Thus This project aims at providing a library to manipulate ABI +Thus the project aims at providing a library to manipulate ABI corpuses, compare them, provide detailed information about their differences and help build tools to infer interesting conclusions about these differences. @@ -47,7 +47,7 @@ the moment the dependencies are the following Free Software packages: \li libxml2 \li autoconf - + \li libzip Then go to your local libabigail.git directory where the source code you've checked out lies and create a 'build' directory that will diff --git a/include/Makefile.am b/include/Makefile.am index b9fc8979..0c328ad7 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -2,6 +2,7 @@ headers = \ abg-ir.h \ abg-corpus.h \ abg-libxml-utils.h \ +abg-libzip-utils.h \ abg-fwd.h \ abg-hash.h \ abg-config.h \ diff --git a/include/abg-corpus.h b/include/abg-corpus.h index 635f7e7b..be10b9dc 100644 --- a/include/abg-corpus.h +++ b/include/abg-corpus.h @@ -37,13 +37,13 @@ namespace abigail class corpus { public: + struct impl; typedef std::string string; typedef shared_ptr translation_unit_sptr; typedef std::vector translation_units; private: - string m_name; - translation_units m_members; + shared_ptr m_priv; corpus(); @@ -57,8 +57,24 @@ public: const translation_units& get_translation_units() const; + void + drop_translation_units(); + + string& + get_file_path() const; + + void + set_file_path(const string&); + bool is_empty() const; + + bool + write() const; + + int + read(); + }; }//end namespace abigail #endif //__ABG_CORPUS_H__ diff --git a/include/abg-libxml-utils.h b/include/abg-libxml-utils.h index c05c1e30..44bdb7f2 100644 --- a/include/abg-libxml-utils.h +++ b/include/abg-libxml-utils.h @@ -54,6 +54,7 @@ struct charDeleter typedef shared_ptr xml_char_sptr; reader_sptr new_reader_from_file(const std::string& path); +reader_sptr new_reader_from_buffer(const std::string& buffer); xml_char_sptr build_xml_char_sptr(xmlChar*); template diff --git a/include/abg-libzip-utils.h b/include/abg-libzip-utils.h new file mode 100644 index 00000000..6eaefc03 --- /dev/null +++ b/include/abg-libzip-utils.h @@ -0,0 +1,72 @@ +// -*- mode: C++ -*- +// +// Copyright (C) 2013 Red Hat, Inc. +// +// This file is part of the GNU Application Binary Interface Generic +// Analysis and Instrumentation Library (libabigail). This library is +// free software; you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) any +// later version. + +// This library 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 Lesser Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this program; see the file COPYING-LGPLV3. If +// not, see . + +/// @file + +#include +#include + +namespace abigail +{ + +namespace zip_utils +{ + +using std::tr1::shared_ptr; +using std::string; + +/// @brief Functor passed to shared_ptr constructor during +/// instantiation with zip* +/// +/// Its aim is to delete zip* managed by shared_ptr. +struct archive_deleter +{ + void + operator()(zip* archive) + { + /// ??? Maybe check the return code of close and throw if the + /// close fails? But then callers must be prepared to handle + /// this. + zip_close(archive); + } +};//end archive_deleter + +/// @brief Functor passed to shared_ptr's constructor. +/// +/// Its aim is to close (actually delete) the zip_file* managed by the +/// shared_ptr. +struct zip_file_deleter +{ + void + operator()(zip_file*f) + { + zip_fclose(f); + } +}; + +typedef shared_ptr zip_sptr; +zip_sptr open_archive(const string& path, int flags, int *errorp); + +typedef shared_ptr zip_file_sptr; +zip_file_sptr open_file_in_archive(zip_sptr archive, + int file_index); + +}// end namespace zip +}// end namespace abigail diff --git a/src/Makefile.am b/src/Makefile.am index 15ff0343..db19b511 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,8 +4,10 @@ libabigaildir=$(libdir)/ libabigail_la_SOURCES = $(headers) \ $(h)/abg-ir.cc \ +$(h)/abg-corpus.cc \ $(h)/abg-reader.cc \ $(h)/abg-libxml-utils.cc \ +$(h)/abg-libzip-utils.cc \ $(h)/abg-hash.cc \ $(h)/abg-writer.cc \ $(h)/abg-config.cc \ @@ -13,7 +15,7 @@ $(h)/abg-viz-common.cc \ $(h)/abg-viz-dot.cc \ $(h)/abg-viz-svg.cc -libabigail_la_LDFLAGS=@XML_LIBS@ -Wl,--as-needed +libabigail_la_LDFLAGS=@DEPS_LIBS@ -Wl,--as-needed -AM_CPPFLAGS=@XML_CFLAGS@ -Wall -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CPPFLAGS=@DEPS_CPPFLAGS@ -Wall -I$(abs_top_srcdir)/include -I$(abs_top_builddir)/include -I$(abs_top_builddir) AM_CXXFLAGS="-std=gnu++11" diff --git a/src/abg-corpus.cc b/src/abg-corpus.cc new file mode 100644 index 00000000..0929c50d --- /dev/null +++ b/src/abg-corpus.cc @@ -0,0 +1,320 @@ +// -*- mode: C++ -*- +// +// Copyright (C) 2013 Red Hat, Inc. +// +// This file is part of the GNU Application Binary Interface Generic +// Analysis and Instrumentation Library (libabigail). This library is +// free software; you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) any +// later version. + +// This library 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 Lesser Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this program; see the file COPYING-LGPLV3. If +// not, see . + +/// @file + +#include +#include +#include +#include +#include +#include "abg-ir.h" +#include "abg-corpus.h" +#include "abg-libzip-utils.h" + +namespace abigail +{ + +using std::ostringstream; +using std::list; +using std::vector; +using zip_utils::zip_sptr; +using zip_utils::zip_file_sptr; +using zip_utils::open_archive; +using zip_utils::open_file_in_archive; + +template +struct array_deleter +{ + void + operator()(T* a) + { + delete [] a; + } +};//end array_deleter + +struct corpus::impl +{ + string path; + translation_units members; + vector serialized_tus; + mutable zip_sptr archive; + + impl(const string &p) + : path(p) + {} + + zip_sptr + get_archive() const + { + int error_code = 0; + if (!archive) + { + // Open the zip archive. If no archive at that path existed, + // create a new archive. + archive = open_archive(path, ZIP_CREATE|ZIP_CHECKCONS, &error_code); + if (error_code) + { + std::ostringstream o; + o << "zip_create returned error code '" << error_code << "'"; + if (archive) + o << " and returned a non-null archive"; + throw std::runtime_error(o.str()); + } + } + return archive; + } + + /// Closes the zip archive associated to the current corpus, if any. + /// + /// Note that closing the archive writes the content (that was added + /// to it) to the disk and frees the memory associated to the + /// ressources held by the archive. + void + close_archive() + { + if (archive) + archive.reset(); + } + + /// @brief Write a translation unit to the current zip archive + /// associated to to the current corpus. + /// + /// If the translation unit was already present (and loaded) in the + /// current archive, this function replaces the version in the + /// archive with the translation unit given in parameter, otherwise, + /// the translation unit given in parameter is just added to the + /// current archive. Note that the updated archive is not saved to + /// disk until the fonction close_archive() is invoked. + bool + write_tu_to_archive(const translation_unit& tu) + { + ostringstream os; + + zip_sptr ar = get_archive(); + if (!archive) + return false; + + if (!tu.write(os)) + return false; + + serialized_tus.push_back(os.str()); + + zip_source *source; + if ((source = zip_source_buffer(ar.get(), + serialized_tus.back().c_str(), + serialized_tus.back().size(), + false)) == 0) + return false; + + + + int index = zip_name_locate(ar.get(), tu.get_path().c_str(), 0); + if ( index == -1) + { + if (zip_add(ar.get(), tu.get_path().c_str(), source) < 0) + { + zip_source_free(source); + return false; + } + } + else + { + if (zip_replace(ar.get(), index, source) != 0) + { + zip_source_free(source); + return false; + } + } + + return true; + } + + /// Read a file that is at a particular index in the archive, into a + /// translation_unit. + /// + /// @param tu the translation unit to read the content of the file + /// into. + /// + /// @param file_index the index of the file to read in. + /// + /// @return true upon successful completion, false otherwise. + bool + read_to_translation_unit(translation_unit& tu, + int file_index) + { + zip_sptr ar = get_archive(); + if (!ar) + return false; + + zip_file_sptr f = open_file_in_archive(ar, file_index); + if (!f) + return false; + + string input; + { + // Allocate a 64K byte buffer to read the archive. + int buf_size = 64 * 1024; + shared_ptr buf(new char[buf_size + 1], array_deleter()); + memset(buf.get(), 0, buf_size + 1); + input.reserve(buf_size); + + while (zip_fread(f.get(), buf.get(), buf_size)) + { + input.append(buf.get()); + memset(buf.get(), 0, buf_size + 1); + } + } + + if (!tu.read(input)) + return false; + + return true; + } + + private: + impl(); + }; + + /// @param path the path to the file containing the ABI corpus. + corpus::corpus(const string& path) + { + m_priv.reset(new impl(path)); + } + + /// Add a translation unit to the current ABI Corpus. Next time + /// corpus::save is called, all the translation unit that got added + /// to the corpus are going to be serialized on disk in the file + /// associated to the current corpus. + /// + /// @param tu + void + corpus::add(const translation_unit_sptr tu) + { + m_priv->members.push_back(tu); + } + + /// Return the list of translation units of the current corpus. + /// + /// @return the list of translation units of the current corpus. + const corpus::translation_units& + corpus::get_translation_units() const + { + return m_priv->members; + } + +/// Erase the translation units contained in this in-memory object. +/// +/// Note that the on-disk archive file that contains the serialized +/// representation of this object is not modified. +void +corpus::drop_translation_units() +{ + m_priv->members.clear(); +} + + /// Get the file path associated to the corpus file. + /// + /// A subsequent call to corpus::read will deserialize the content of + /// the abi file expected at this path; likewise, a call to + /// corpus::write will serialize the translation units contained in + /// the corpus object into the on-disk file at this path. + + /// @return the file path associated to the current corpus. + string& + corpus::get_file_path() const + { + return m_priv->path; + } + +/// Set the file path associated to the corpus file. +/// +/// A subsequent call to corpus::read will deserialize the content of +/// the abi file expected at this path; likewise, a call to +/// corpus::write will serialize the translation units contained in +/// the corpus object into the on-disk file at this path. +/// @param the new file path to assciate to the current corpus. +void +corpus::set_file_path(const string& path) +{ + m_priv->path = path; +} + + /// Tests if the corpus contains no translation unit. + /// + /// @return true if the corpus contains no translation unit. + bool + corpus::is_empty() const + { + return m_priv->members.empty(); + } + + /// Serialize the current corpus to disk in a file which path is given + /// by corpus::get_file_path. + /// + /// @return true upon successful completion, false otherwise. +bool +corpus::write() const +{ + for (translation_units::const_iterator i = get_translation_units().begin(); + i != get_translation_units().end(); + ++i) + { + if (! m_priv->write_tu_to_archive(**i)) + return false; + } + // TODO: ensure abi-info descriptor is added to the archive. + m_priv->close_archive(); + return true; +} + +/// Open the archive which path is given by corpus::get_file_path and +/// de-serialize each of the translation units it contains. +/// +/// @return the number of entries read and properly de-serialized, +/// zero if none was red. +int +corpus::read() +{ + zip_sptr ar = m_priv->get_archive(); + if (!ar) + return false; + + int nb_of_tu_read = 0; + int nb_entries = zip_get_num_entries(ar.get(), 0); + if (nb_entries < 0) + return 0; + + // TODO: ensure abi-info descriptor is present in the archive. Read + // it and ensure that version numbers match. + for (int i = 0; i < nb_entries; ++i) + { + shared_ptr + tu(new translation_unit(zip_get_name(ar.get(), i, 0))); + if (m_priv->read_to_translation_unit(*tu, i)) + { + add(tu); + ++nb_of_tu_read; + } + } + return nb_of_tu_read; +} + +}// end namespace abigail diff --git a/src/abg-ir.cc b/src/abg-ir.cc index fd38b17e..c35f8134 100644 --- a/src/abg-ir.cc +++ b/src/abg-ir.cc @@ -152,6 +152,16 @@ translation_unit::get_path() const return m_path; } +/// Set the path associated to the current instance of +/// translation_unit. +/// +/// @param a_path the new path to set. +void +translation_unit::set_path(const string& a_path) +{ + m_path = a_path; +} + /// Getter of the location manager for the current translation unit. /// /// @return a reference to the location manager for the current diff --git a/src/abg-libxml-utils.cc b/src/abg-libxml-utils.cc index e68ea192..a4eea206 100644 --- a/src/abg-libxml-utils.cc +++ b/src/abg-libxml-utils.cc @@ -41,6 +41,21 @@ new_reader_from_file(const std::string& path) return p; } +/// Instanciate an xmlTextReader that parses the content of an +/// in-memory buffer, wrap it into a smart pointer and return it. +/// +/// @param buffer the in-memory buffer to be parsed by the returned +/// instance of xmlTextReader. +reader_sptr +new_reader_from_buffer(const std::string& buffer) +{ + reader_sptr p = + build_sptr(xmlReaderForMemory(buffer.c_str(), + buffer.length(), + "", 0, 0)); + return p; +} + /// Build and return a shared_ptr for a pointer to xmlTextReader template<> shared_ptr diff --git a/src/abg-libzip-utils.cc b/src/abg-libzip-utils.cc new file mode 100644 index 00000000..c1d7414c --- /dev/null +++ b/src/abg-libzip-utils.cc @@ -0,0 +1,73 @@ +// -*- mode: C++ -*- +// +// Copyright (C) 2013 Red Hat, Inc. +// +// This file is part of the GNU Application Binary Interface Generic +// Analysis and Instrumentation Library (libabigail). This library is +// free software; you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) any +// later version. + +// This library 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 Lesser Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this program; see the file COPYING-LGPLV3. If +// not, see . + +/// @file + +#include +#include "abg-libzip-utils.h" + +namespace abigail +{ + +namespace zip_utils +{ + +/// This is a wrapper of the zip_open function, from libzip. Its +/// purpose is to return a zip pointer wrapped in an appropriate +/// shared_ptr and thus to free the caller from having to deal with +/// calling zip_close on it. +/// +/// The arguments this wrapper have the same meaning as in zip_open. +/// +/// @param path the path to the zip archive to open. +/// +/// @return a non-null zip pointer if the function succeeds. +zip_sptr +open_archive(const string& path, int flags, int *errorp) +{ + zip* z = zip_open(path.c_str(), flags, errorp); + if (!z) + return zip_sptr(); + return zip_sptr(z, archive_deleter()); +} + +/// @brief Open a file from a zip archive. +/// +/// Open the file that is at a given \a index in the \a archive. +/// +/// @param archive the zip archive to consider +/// +/// @param file_index the index of the file to open from the zip +/// \a archive. +/// +/// @return a non-null zip_file* upon successful completion, a null +/// pointer otherwise. +zip_file_sptr +open_file_in_archive(zip_sptr archive, + int file_index) +{ + zip_file * f = zip_fopen_index(archive.get(), file_index, 0); + if (!f) + return zip_file_sptr(); + return zip_file_sptr(f, zip_file_deleter()); +} + +}// end namespace zip +}// end namespace abigail diff --git a/src/abg-reader.cc b/src/abg-reader.cc index 7adc7f23..17de8723 100644 --- a/src/abg-reader.cc +++ b/src/abg-reader.cc @@ -2618,4 +2618,17 @@ translation_unit::read() reader::read_context read_ctxt(xml::new_reader_from_file(this->get_path())); return reader::read_input(read_ctxt, *this); } + +/// Deserialize the contents of an in-memory buffer into this +/// translation_unit object. +/// +///@param buffer the in-memory buffer to de-serialize from. +/// +/// @return true upon successful de-serialization, false otherwise. +bool +translation_unit::read(const string& buffer) +{ + reader::read_context read_ctxt(xml::new_reader_from_buffer(buffer)); + return reader::read_input(read_ctxt, *this); +} }//end namespace abigail diff --git a/src/abg-writer.cc b/src/abg-writer.cc index df8895a6..4170c82e 100644 --- a/src/abg-writer.cc +++ b/src/abg-writer.cc @@ -20,27 +20,29 @@ /// @file -#include +#include +#include #include -#include #include #include "abg-config.h" #include "abg-ir.h" namespace abigail { - -/// Internal namespace for writer. -namespace writer -{ +using std::cerr; using std::tr1::shared_ptr; using std::tr1::dynamic_pointer_cast; using std::tr1::static_pointer_cast; +using std::ofstream; using std::ostream; using std::ostringstream; using std::list; using std::tr1::unordered_map; +/// Internal namespace for writer. +namespace writer +{ + class id_manager { unsigned long long m_cur_id; @@ -1487,11 +1489,48 @@ write_class_tdecl(const shared_ptr decl, } //end namespace writer +/// Serialize the contents of this translation unit object into an +/// output stream. +/// +/// @param out the output stream. bool -translation_unit::write(std::ostream &out) +translation_unit::write(std::ostream &out) const { writer::write_context ctxt(out); return writer::write_translation_unit(*this, ctxt, /*indent=*/0); } +/// Serialize the contents of this translation unit object into an +/// external file. +/// +/// @param out the path to the external file. +bool +translation_unit::write(const string& path) const +{ + bool result = true; + + try + { + ofstream of(path, std::ios_base::trunc); + if (!of.is_open()) + { + cerr << "failed to access " << path << "\n"; + return false; + } + + if (!write(of)) + { + cerr << "failed to access " << path << "\n"; + result = false; + } + + of.close(); + } + catch(...) + { + result = false; + } + return result; +} + } //end namespace abigail diff --git a/tests/Makefile.am b/tests/Makefile.am index c4cafac3..d72219af 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -2,6 +2,7 @@ h=$(abs_srcdir) TESTS= \ runtestreadwrite \ +runtestwritereadarchive \ runtestsvg \ runtestdot @@ -9,8 +10,8 @@ noinst_PROGRAMS= $(TESTS) testwalker noinst_LTLIBRARIES = libtestutils.la -libtestutils_la_SOURCES=\ -test-utils.h \ +libtestutils_la_SOURCES= \ +test-utils.h \ test-utils.cc libtestutils_la_CXXFLAGS= \ @@ -20,6 +21,9 @@ libtestutils_la_CXXFLAGS= \ runtestreadwrite_SOURCES=$(h)/test-read-write.cc runtestreadwrite_LDADD=libtestutils.la $(top_builddir)/src/libabigail.la +runtestwritereadarchive_SOURCES=$(h)/test-write-read-archive.cc +runtestwritereadarchive_LDADD=libtestutils.la $(top_builddir)/src/libabigail.la + runtestsvg_SOURCES=$(h)/test-svg.cc runtestsvg_LDADD=$(top_builddir)/src/libabigail.la @@ -49,7 +53,13 @@ data/test-read-write/test12.xml \ data/test-read-write/test13.xml \ data/test-read-write/test14.xml \ data/test-read-write/test15.xml \ -data/test-read-write/test16.xml +data/test-read-write/test16.xml \ +\ +data/test-write-read-archive/test0.xml \ +data/test-write-read-archive/test1.xml \ +data/test-write-read-archive/test2.xml \ +data/test-write-read-archive/test3.xml \ +data/test-write-read-archive/test4.xml clean-local: clean-local-check .PHONY: clean-local-check diff --git a/tests/data/test-write-read-archive/test0.xml b/tests/data/test-write-read-archive/test0.xml new file mode 100644 index 00000000..41c7ddce --- /dev/null +++ b/tests/data/test-write-read-archive/test0.xml @@ -0,0 +1,3 @@ + + + diff --git a/tests/data/test-write-read-archive/test1.xml b/tests/data/test-write-read-archive/test1.xml new file mode 100644 index 00000000..13fea53d --- /dev/null +++ b/tests/data/test-write-read-archive/test1.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/data/test-write-read-archive/test2.xml b/tests/data/test-write-read-archive/test2.xml new file mode 100644 index 00000000..d26b0b3c --- /dev/null +++ b/tests/data/test-write-read-archive/test2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/tests/data/test-write-read-archive/test3.xml b/tests/data/test-write-read-archive/test3.xml new file mode 100644 index 00000000..d9862ddb --- /dev/null +++ b/tests/data/test-write-read-archive/test3.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/data/test-write-read-archive/test4.xml b/tests/data/test-write-read-archive/test4.xml new file mode 100644 index 00000000..1e74e780 --- /dev/null +++ b/tests/data/test-write-read-archive/test4.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/test-write-read-archive.cc b/tests/test-write-read-archive.cc new file mode 100644 index 00000000..35dc6cf7 --- /dev/null +++ b/tests/test-write-read-archive.cc @@ -0,0 +1,176 @@ +// -*- Mode: C++ -*- +// +// Copyright (C) 2013 Red Hat, Inc. +// +// This file is part of the GNU Application Binary Interface Generic +// Analysis and Instrumentation Library (libabigail). This library is +// free software; you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) any +// later version. + +// This library 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 Lesser Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this program; see the file COPYING-LGPLV3. If +// not, see . + +#include +#include +#include +#include "test-utils.h" +#include "abg-ir.h" +#include "abg-corpus.h" + +struct InOutSpec +{ + const char* in_path; + const char* out_path; +};// end struct InOutSpec + +/// This is an aggregate that specifies where the test gets the +/// elements that it reads to build an archive. It also specifies +/// where to write the output result of the element that is written +/// back to disk, for diffing purposes. +InOutSpec archive_elements[] = +{ + { + "data/test-write-read-archive/test0.xml", + "output/test-write-read-archive/test0.xml", + }, + { + "data/test-write-read-archive/test1.xml", + "output/test-write-read-archive/test2.xml", + }, + { + "data/test-write-read-archive/test2.xml", + "output/test-write-read-archive/test2.xml", + }, + { + "data/test-write-read-archive/test3.xml", + "output/test-write-read-archive/test3.xml", + }, + { + "data/test-write-read-archive/test4.xml", + "output/test-write-read-archive/test4.xml", + }, + // This should be the last entry. + {NULL, NULL} +}; + +#define NUM_ARCHIVES_ELEMENTS \ + ((sizeof(archive_elements) / sizeof(InOutSpec)) -1) + +/// Where to write the archive, and where to read it from to get the +/// base for the diffing. +const InOutSpec archive_spec = +{ + "data/test-write-read-archive/archive.abi", + "output/test-write-read-archive/archive.abi" +}; + +using std::string; +using std::cerr; +using std::ofstream; +using std::tr1::shared_ptr; +using abigail::corpus; +using abigail::translation_unit; + +int +main() +{ + // Read the elements into abigail::translation_unit and stick them + // into an abigail::corpus. + string in_path, out_path; + bool is_ok = true; + + out_path = + abigail::tests::get_build_dir() + "/tests/" + archive_spec.out_path; + + if (!abigail::tests::ensure_parent_dir_created(out_path)) + { + cerr << "Could not create parent director for " << out_path; + return 1; + } + corpus abi_corpus(out_path); + + for (InOutSpec *s = archive_elements; s->in_path; ++s) + { + in_path = abigail::tests::get_src_dir() + "/tests/" + s->in_path; + shared_ptr tu(new translation_unit(in_path)); + if (!tu->read()) + { + cerr << "failed to read " << in_path << "\n"; + is_ok = false; + continue; + } + abi_corpus.add(tu); + } + + if (!abi_corpus.write()) + { + cerr << "failed to write archive file: " << abi_corpus.get_file_path(); + return 1; + } + + // Diff the archive members. + // + // Basically, re-read the corpus from disk, walk the loaded + // translation units, write them back and diff them against their + // reference. + + abi_corpus.drop_translation_units(); + if (abi_corpus.get_translation_units().size()) + { + cerr << "In-memory object of abi corpus at '" + << abi_corpus.get_file_path() + << "' still has translation units after call to " + "corpus::drop_translation_units!"; + return false; + } + + if (!abi_corpus.read()) + { + cerr << "Failed to load the abi corpus from path '" + << abi_corpus.get_file_path() + << "'"; + return 1; + } + + if (abi_corpus.get_translation_units().size() != NUM_ARCHIVES_ELEMENTS) + { + cerr << "Read " << abi_corpus.get_translation_units().size() + << " elements from the abi corpus at " + << abi_corpus.get_file_path() + << " instead of " + << NUM_ARCHIVES_ELEMENTS; + return 1; + } + + for (unsigned i = 0; i < NUM_ARCHIVES_ELEMENTS; ++i) + { + InOutSpec& spec = archive_elements[i]; + out_path = + abigail::tests::get_build_dir() + "/tests/" + spec.out_path; + + if (!abi_corpus.get_translation_units()[i]->write(out_path)) + { + cerr << "Failed to serialize translation_unit to '" + << out_path + << "'\n"; + is_ok = false; + } + + string ref = + abigail::tests::get_src_dir() + "/tests/" + spec.in_path; + string cmd = "diff -u " + ref + " " + out_path; + + if (system(cmd.c_str())) + is_ok = false; + } + + return !is_ok; +}