From 7f941f990aa436c7d736f19a2f5579cd85933ec6 Mon Sep 17 00:00:00 2001
From: "jimblandy@gmail.com"
 <jimblandy@gmail.com@4c0a9323-5329-0410-9bdc-e9ce6186880e>
Date: Tue, 15 Dec 2009 16:54:44 +0000
Subject: Linux dumper: Add unit tests for google_breakpad::StabsReader.

The test system is based on Google C++ Testing Framework and the
Google C++ Mocking Framework.

This includes a parser that turns human-readable input files ("mock
stabs") into .stab and .stabstr section contents, which we can then
pass to a StabsReader instance, using a handler object written with
GoogleMock. The 'make check' target in src/tools/linux/dump_syms runs
this.

The supplied input file is pretty small, but I've done coverage
testing, and it does cover the parser.

I thought the mock stabs parser would be less elaborate than it turned
out to be. Lesson learned.

a=jimblandy, r=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@444 4c0a9323-5329-0410-9bdc-e9ce6186880e
---
 src/common/linux/stabs_reader_unittest.cc          | 548 +++++++++++++++++++++
 .../linux/testdata/stabs_reader_unittest.input1    |  19 +
 .../linux/testdata/stabs_reader_unittest.input2    |   1 +
 .../linux/testdata/stabs_reader_unittest.input3    |   2 +
 .../linux/testdata/stabs_reader_unittest.input4    |   1 +
 .../linux/testdata/stabs_reader_unittest.input5    |   2 +
 src/tools/linux/dump_syms/Makefile                 |  34 ++
 7 files changed, 607 insertions(+)
 create mode 100644 src/common/linux/stabs_reader_unittest.cc
 create mode 100644 src/common/linux/testdata/stabs_reader_unittest.input1
 create mode 100644 src/common/linux/testdata/stabs_reader_unittest.input2
 create mode 100644 src/common/linux/testdata/stabs_reader_unittest.input3
 create mode 100644 src/common/linux/testdata/stabs_reader_unittest.input4
 create mode 100644 src/common/linux/testdata/stabs_reader_unittest.input5

(limited to 'src')

diff --git a/src/common/linux/stabs_reader_unittest.cc b/src/common/linux/stabs_reader_unittest.cc
new file mode 100644
index 00000000..836b3a4d
--- /dev/null
+++ b/src/common/linux/stabs_reader_unittest.cc
@@ -0,0 +1,548 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// stabs_reader_unittest.cc: Unit tests for StabsReader.
+
+#include <a.out.h>
+#include <cassert>
+#include <cerrno>
+#include <cstdarg>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <stab.h>
+
+#include "breakpad_googletest_includes.h"
+#include "common/linux/stabs_reader.h"
+
+using std::istream;
+using std::istringstream;
+using std::map;
+using std::ostream;
+using std::ostringstream;
+using std::string;
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::Sequence;
+using ::testing::StrEq;
+
+using google_breakpad::StabsHandler;
+using google_breakpad::StabsReader;
+
+namespace {
+
+// Mock stabs file parser
+//
+// In order to test StabsReader, we parse a human-readable input file
+// describing STABS entries into in-memory .stab and .stabstr
+// sections, and then pass those to StabsReader to look at. The
+// human-readable file is called a "mock stabs file".
+//
+// Each line of a mock stabs file should have the following form:
+//
+//   TYPE OTHER DESC VALUE NAME
+// 
+// where all data is Latin-1 bytes and fields are separated by single
+// space characters, except for NAME, which may contain spaces and
+// continues to the end of the line.  The fields have the following
+// meanings:
+// 
+// - TYPE: the name of the stabs symbol type; like SO or FUN.  These are
+//   the names from /usr/include/bits/stab.def, without the leading N_.
+//
+// - OTHER, DESC, VALUE: numeric values for the n_other, n_desc, and
+//   n_value fields of the stab.  These can be decimal or hex,
+//   using C++ notation (10, 0x10)
+//   
+// - NAME: textual data for the entry.  STABS packs all kinds of
+//   interesting data into entries' NAME fields, so calling it a NAME
+//   is misleading, but that's how it is.  For SO, this may be a
+//   filename; for FUN, this is the function name, plus type data; and
+//   so on.
+
+// I don't know if the whole parser/handler pattern is really worth
+// the bureaucracy in this case.  But just writing it out as
+// old-fashioned functions wasn't astonishingly clear either, so it
+// seemed worth a try.
+
+// A handler class for mock stabs data.
+class MockStabsHandler {
+ public:
+  MockStabsHandler() { }
+  virtual ~MockStabsHandler() { }
+  // The mock stabs parser calls this member function for each entry
+  // it parses, passing it the contents of the entry.  If this function
+  // returns true, the parser continues; if it returns false, the parser
+  // stops, and its Process member function returns false.
+  virtual bool Entry(enum __stab_debug_code type, char other, short desc,
+                     unsigned long value, const string &name) { return true; }
+  // Report an error in parsing the mock stabs data.  If this returns true,
+  // the parser continues; if it returns false, the parser stops and
+  // its Process member function returns false.
+  virtual bool Error(const char *format, ...) = 0;
+};
+
+// A class for parsing mock stabs files.
+class MockStabsParser {
+ public:
+  // Create a parser reading input from STREAM and passing data to HANDLER.
+  // Use FILENAME when reporting errors.
+  MockStabsParser(const string &filename, istream *stream,
+                  MockStabsHandler *handler);
+  // Parse data from the STREAM, invoking HANDLER->Entry for each
+  // entry we get.  Return true if we parsed all the data succesfully,
+  // or false if we stopped early because Entry returned false, or if
+  // there were any errors during parsing.
+  bool Process();
+ private:
+  // A type for maps from stab type names ("SO", "SLINE", etc.) to
+  // n_type values.
+  typedef map<string, unsigned char> StabTypeNameTable;
+
+  // Initialize the table mapping STAB type names to n_type values.
+  void InitializeTypeNames();
+
+  // Parse LINE, one line of input from a mock stabs file, and pass
+  // its contents to handler_->Entry and return the boolean value that
+  // returns.  If we encounter an error parsing the line, report it
+  // using handler->Error.
+  bool ParseLine(const string &line);
+
+  const string &filename_;
+  istream *stream_;
+  MockStabsHandler *handler_;
+  int line_number_;
+  StabTypeNameTable type_names_;
+};
+
+MockStabsParser::MockStabsParser(const string &filename, istream *stream,
+                                 MockStabsHandler *handler):
+    filename_(filename), stream_(stream), handler_(handler),
+    line_number_(0) {
+  InitializeTypeNames();
+}
+
+bool MockStabsParser::Process() {
+  // Iterate once per line, including a line at EOF without a
+  // terminating newline.
+  for(;;) {
+    string line;
+    std::getline(*stream_, line, '\n');
+    if (line.empty() && stream_->eof())
+      break;
+    line_number_++;
+    if (! ParseLine(line))
+      return false;
+  }
+  return true;
+}
+
+void MockStabsParser::InitializeTypeNames() {
+  // On GLIBC-based systems, <bits/stab.def> is a file containing a
+  // call to an unspecified macro __define_stab for each stab type.
+  // <stab.h> uses it to define the __stab_debug_code enum type.  We
+  // use it here to initialize our mapping from type names to enum
+  // values.
+  //
+  // This isn't portable to non-GLIBC systems.  Feel free to just
+  // hard-code the values if this becomes a problem.
+#  define __define_stab(name, code, str) type_names_[string(str)] = code;
+#  include <bits/stab.def>
+#  undef __define_stab
+}
+
+bool MockStabsParser::ParseLine(const string &line) {
+  istringstream linestream(line);
+  // Allow "0x" prefix for hex, and so on.
+  linestream.unsetf(istringstream::basefield);
+  // Parse and validate the stabs type.
+  string typeName;
+  linestream >> typeName;
+  StabTypeNameTable::const_iterator typeIt = type_names_.find(typeName);
+  if (typeIt == type_names_.end())
+    return handler_->Error("%s:%d: unrecognized stab type: %s\n",
+                           filename_.c_str(), line_number_, typeName.c_str());
+  // These are int, not char and unsigned char, to ensure they're parsed
+  // as decimal numbers, not characters.
+  int otherInt, descInt;
+  unsigned long value;
+  linestream >> otherInt >> descInt >> value;
+  if (linestream.fail())
+    return handler_->Error("%s:%d: malformed mock stabs input line\n",
+                           filename_.c_str(), line_number_);
+  if (linestream.peek() == ' ')
+    linestream.get();
+  string name;
+  getline(linestream, name, '\n');
+  return handler_->Entry(static_cast<__stab_debug_code>(typeIt->second),
+                         otherInt, descInt, value, name);
+}
+
+// A class for constructing .stab sections.
+//
+// A .stab section is an array of struct nlist entries.  These
+// entries' n_un.n_strx fields are indices into an accompanying
+// .stabstr section.
+class StabSection {
+ public:
+  StabSection(): used_(0), size_(1) {
+    entries_ = (struct nlist *) malloc(sizeof(*entries_) * size_);
+  }
+  ~StabSection() { free(entries_); }
+
+  // Append a new 'struct nlist' entry to the end of the section, and
+  // return a pointer to it.  This pointer is valid until the next
+  // call to Append.  The caller should initialize the returned entry
+  // as needed.
+  struct nlist *Append();
+  // Set SECTION to the contents of a .stab section holding the
+  // accumulated list of entries added with Append.
+  void GetSection(string *section);
+
+ private:
+  // The array of stabs entries,
+  struct nlist *entries_;
+  // The number of elements of entries_ that are used, and the allocated size
+  // of the array.
+  size_t used_, size_;
+};
+
+struct nlist *StabSection::Append() {
+  if (used_ == size_) {
+    size_ *= 2;
+    entries_ = (struct nlist *) realloc(entries_, sizeof(*entries_) * size_);
+  }
+  assert(used_ < size_);
+  return &entries_[used_++];
+}
+
+void StabSection::GetSection(string *section) {
+  section->assign(reinterpret_cast<char *>(entries_),
+                  sizeof(*entries_) * used_);
+}
+
+// A class for building .stabstr sections.
+// 
+// A .stabstr section is an array of characters containing a bunch of
+// null-terminated strings.  A string is identified by the index of
+// its initial character in the array.  The array always starts with a
+// null byte, so that an index of zero refers to the empty string.
+//
+// This implementation also ensures that if two strings are equal, we
+// assign them the same indices; most linkers do this, and some
+// clients may rely upon it.  (Note that this is not quite the same as
+// ensuring that a string only appears once in the section; you could
+// share space when one string is a suffix of another, but we don't.)
+class StabstrSection {
+ public:
+  StabstrSection(): next_byte_(1) { string_indices_[""] = 0; }
+  // Ensure STR is present in the string section, and return its index.
+  size_t Insert(const string &str);
+  // Set SECTION to the contents of a .stabstr section in which the
+  // strings passed to Insert appear at the indices we promised.
+  void GetSection(string *section);
+ private:
+  // Maps from strings to .stabstr indices and back.
+  typedef map<string, size_t> StringToIndex;
+  typedef map<size_t, const string *> IndexToString;
+
+  // A map from strings to the indices we've assigned them.
+  StringToIndex string_indices_;
+
+  // The next unused byte in the section.  The next string we add
+  // will get this index.
+  size_t next_byte_;
+};
+
+size_t StabstrSection::Insert(const string &str) {
+  StringToIndex::iterator it = string_indices_.find(str);
+  size_t index;
+  if (it != string_indices_.end()) {
+    index = it->second;
+  } else {
+    // This is the first time we've seen STR; add it to the table.
+    string_indices_[str] = next_byte_;
+    index = next_byte_;
+    next_byte_ += str.size() + 1;
+  }
+  return index;
+}
+
+void StabstrSection::GetSection(string *section) {
+  // First we have to invert the map.
+  IndexToString byIndex;
+  for (StringToIndex::const_iterator it = string_indices_.begin();
+       it != string_indices_.end(); it++)
+    byIndex[it->second] = &it->first;
+  // Now we build the .stabstr section.
+  section->clear();
+  for (IndexToString::const_iterator it = byIndex.begin();
+       it != byIndex.end(); it++) {
+    // Make sure we're actually assigning it the index we claim to be.
+    assert(it->first == section->size());
+    *section += *(it->second);
+    *section += '\0';
+  }
+}
+
+// A mock stabs parser handler class that builds .stab and .stabstr
+// sections.
+class StabsSectionsBuilder: public MockStabsHandler {
+ public:
+  // Construct a handler that will receive data from a MockStabsParser
+  // and construct .stab and .stabstr sections.  FILENAME should be
+  // the name of the mock stabs input file; we use it in error
+  // messages.
+  StabsSectionsBuilder(const string &filename):
+      filename_(filename), error_count_(0) { }
+
+  // Overridden virtual member functions.
+  bool Entry(enum __stab_debug_code type, char other, short desc,
+             unsigned long value, const string &name);
+  virtual bool Error(const char *format, ...);
+
+  // Set SECTION to the contents of a .stab or .stabstr section
+  // reflecting the entries that have been passed to us via Entry.
+  void GetStab(string *section);
+  void GetStabstr(string *section);
+
+ private:
+  StabSection stab_;                    // stabs entries we've seen
+  StabstrSection stabstr_;              // and the strings they love
+  const string &filename_;              // input filename, for error messages
+  int error_count_;                     // number of errors we've seen so far
+};
+
+bool StabsSectionsBuilder::Entry(enum __stab_debug_code type, char other,
+                                 short desc, unsigned long value,
+                                 const string &name) {
+  struct nlist *entry = stab_.Append();
+  entry->n_type = type;
+  entry->n_other = other;
+  entry->n_desc = desc;
+  entry->n_value = value;
+  entry->n_un.n_strx = stabstr_.Insert(name);
+  return true;
+}
+
+bool StabsSectionsBuilder::Error(const char *format, ...) {
+  va_list args;
+  va_start(args, format);
+  vfprintf(stderr, format, args);
+  va_end(args);
+  error_count_++;
+  if (error_count_ >= 20) {
+    fprintf(stderr,
+            "%s: lots of errors; is this really a mock stabs file?\n",
+            filename_.c_str());
+    return false;
+  }
+  return true;
+}
+
+void StabsSectionsBuilder::GetStab(string *section) {
+  stab_.GetSection(section);
+}
+
+void StabsSectionsBuilder::GetStabstr(string *section) {
+  stabstr_.GetSection(section);
+}
+
+class MockStabsReaderHandler: public StabsHandler {
+ public:
+  MOCK_METHOD3(StartCompilationUnit,
+               bool(const char *, uint64_t, const char *));
+  MOCK_METHOD1(EndCompilationUnit, bool(uint64_t));
+  MOCK_METHOD2(StartFunction, bool(const std::string &, uint64_t));
+  MOCK_METHOD1(EndFunction, bool(uint64_t));
+  MOCK_METHOD3(Line, bool(uint64_t, const char *, int));
+  void Warning(const char *format, ...) { MockWarning(format); }
+  MOCK_METHOD1(MockWarning, void(const char *));
+};
+
+// Create a StabsReader to parse the mock stabs data in INPUT_FILE,
+// passing the parsed information to HANDLER. If all goes well, return
+// the result of calling the reader's Process member function.
+// Otherwise, return false. INPUT_FILE should be relative to the top
+// of the source tree.
+static bool ApplyHandlerToMockStabsData(StabsHandler *handler,
+                                        const string &input_file) {
+  string full_input_file
+      = string(getenv("srcdir") ? getenv("srcdir") : ".") + "/" + input_file;
+
+  // Open the input file.
+  std::ifstream stream(full_input_file.c_str());
+  if (stream.fail()) {
+    fprintf(stderr, "error opening mock stabs input file %s: %s\n",
+            full_input_file.c_str(), strerror(errno));
+    return false;
+  }
+
+  // Parse the mock stabs data, and produce stabs sections to use as
+  // test input to the reader.
+  StabsSectionsBuilder builder(full_input_file);
+  MockStabsParser mock_parser(full_input_file, &stream, &builder);
+  if (!mock_parser.Process())
+    return false;
+  string stab, stabstr;
+  builder.GetStab(&stab);
+  builder.GetStabstr(&stabstr);
+
+  // Run the parser on the test input, passing whatever we find to HANDLER.
+  StabsReader reader(
+      reinterpret_cast<const uint8_t *>(stab.data()),    stab.size(),
+      reinterpret_cast<const uint8_t *>(stabstr.data()), stabstr.size(),
+      handler);
+  return reader.Process();
+}
+
+TEST(StabsReaderTestCase, MockStabsInput) {
+  MockStabsReaderHandler mock_handler;
+
+  {
+    InSequence s;
+
+    EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file1.c"), 
+                                                   0x42, StrEq("builddir1/")))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, StartFunction(StrEq("fun1"), 0x62))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, Line(0xe4, StrEq("file1.c"), 91))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, Line(0x164, StrEq("header.h"), 111))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndFunction(0x112))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, StartFunction(StrEq("fun2"), 0x112))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, Line(0x234, StrEq("header.h"), 131))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, Line(0x254, StrEq("file1.c"), 151))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndFunction(0x152))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndCompilationUnit(0x152))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file3.c"), 
+                                                   0x182, NULL))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndCompilationUnit(0x192))
+        .WillOnce(Return(true));
+  }
+
+  ASSERT_TRUE(ApplyHandlerToMockStabsData(
+                  &mock_handler, 
+                  "common/linux/testdata/stabs_reader_unittest.input1"));
+}
+
+TEST(StabsReaderTestCase, AbruptCU) {
+  MockStabsReaderHandler mock_handler;
+
+  {
+    InSequence s;
+
+    EXPECT_CALL(mock_handler,
+                StartCompilationUnit(StrEq("file2-1.c"), 0x12, NULL))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndCompilationUnit(NULL))
+        .WillOnce(Return(true));
+  }
+
+  ASSERT_TRUE(ApplyHandlerToMockStabsData(
+                  &mock_handler, 
+                  "common/linux/testdata/stabs_reader_unittest.input2"));
+}
+
+TEST(StabsReaderTestCase, AbruptFunction) {
+  MockStabsReaderHandler mock_handler;
+
+  {
+    InSequence s;
+
+    EXPECT_CALL(mock_handler,
+                StartCompilationUnit(StrEq("file3-1.c"), 0x12, NULL))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, StartFunction(StrEq("fun3_1"), 0x22))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndFunction(NULL))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndCompilationUnit(NULL))
+        .WillOnce(Return(true));
+  }
+
+  ASSERT_TRUE(ApplyHandlerToMockStabsData(
+                  &mock_handler, 
+                  "common/linux/testdata/stabs_reader_unittest.input3"));
+}
+
+TEST(StabsReaderTestCase, NoCU) {
+  MockStabsReaderHandler mock_handler;
+
+  EXPECT_CALL(mock_handler, StartCompilationUnit(_, _, _))
+      .Times(0);
+  EXPECT_CALL(mock_handler, StartFunction(_, _))
+      .Times(0);
+
+  ASSERT_TRUE(ApplyHandlerToMockStabsData(
+                  &mock_handler, 
+                  "common/linux/testdata/stabs_reader_unittest.input4"));
+  
+}
+
+TEST(StabsReaderTestCase, NoCUEnd) {
+  MockStabsReaderHandler mock_handler;
+
+  {
+    InSequence s;
+
+    EXPECT_CALL(mock_handler,
+                StartCompilationUnit(StrEq("file5-1.c"), 0x12, NULL))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndCompilationUnit(NULL))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler,
+                StartCompilationUnit(StrEq("file5-2.c"), 0x22, NULL))
+        .WillOnce(Return(true));
+    EXPECT_CALL(mock_handler, EndCompilationUnit(NULL))
+        .WillOnce(Return(true));
+  }
+
+  ASSERT_TRUE(ApplyHandlerToMockStabsData(
+                  &mock_handler, 
+                  "common/linux/testdata/stabs_reader_unittest.input5"));
+  
+}
+
+} // anonymous namespace
diff --git a/src/common/linux/testdata/stabs_reader_unittest.input1 b/src/common/linux/testdata/stabs_reader_unittest.input1
new file mode 100644
index 00000000..44b3d0a3
--- /dev/null
+++ b/src/common/linux/testdata/stabs_reader_unittest.input1
@@ -0,0 +1,19 @@
+SO      10   11   0x02 builddir/
+FUN     20   21   0x12 not the SO with source file name we expected 
+SO      30   31   0x22 
+SO      40   41   0x32 builddir1/
+SO      50   51   0x42 file1.c
+LSYM    60   61   0x52 not the FUN we're looking for
+FUN     70   71   0x62 fun1
+BINCL   80   81   0x72 something to ignore in a FUN body
+SLINE   90   91   0x82
+SOL    100  101   0x92 header.h
+SLINE  110  111  0x102
+FUN    120  121  0x112 fun2:some stabs type info here, to trim from the name
+SLINE  130  131  0x122
+SOL    140  141  0x132 file1.c
+SLINE  150  151  0x142
+SO     160  161  0x152
+LSYM   170  171  0x162
+SO     180  181  0x182 file3.c
+SO     190  191  0x192
diff --git a/src/common/linux/testdata/stabs_reader_unittest.input2 b/src/common/linux/testdata/stabs_reader_unittest.input2
new file mode 100644
index 00000000..82efeb92
--- /dev/null
+++ b/src/common/linux/testdata/stabs_reader_unittest.input2
@@ -0,0 +1 @@
+SO      10   11   0x12 file2-1.c
diff --git a/src/common/linux/testdata/stabs_reader_unittest.input3 b/src/common/linux/testdata/stabs_reader_unittest.input3
new file mode 100644
index 00000000..73e6a302
--- /dev/null
+++ b/src/common/linux/testdata/stabs_reader_unittest.input3
@@ -0,0 +1,2 @@
+SO      10   11   0x12 file3-1.c
+FUN     20   21   0x22 fun3_1
diff --git a/src/common/linux/testdata/stabs_reader_unittest.input4 b/src/common/linux/testdata/stabs_reader_unittest.input4
new file mode 100644
index 00000000..dfdbf677
--- /dev/null
+++ b/src/common/linux/testdata/stabs_reader_unittest.input4
@@ -0,0 +1 @@
+SO      10   11   0x12 build-directory/
diff --git a/src/common/linux/testdata/stabs_reader_unittest.input5 b/src/common/linux/testdata/stabs_reader_unittest.input5
new file mode 100644
index 00000000..7a447c88
--- /dev/null
+++ b/src/common/linux/testdata/stabs_reader_unittest.input5
@@ -0,0 +1,2 @@
+SO      10   11   0x12 file5-1.c
+SO      20   21   0x22 file5-2.c
diff --git a/src/tools/linux/dump_syms/Makefile b/src/tools/linux/dump_syms/Makefile
index 61aee177..a0424c05 100644
--- a/src/tools/linux/dump_syms/Makefile
+++ b/src/tools/linux/dump_syms/Makefile
@@ -78,6 +78,40 @@ dump_symbols.o: dump_symbols.cc
 file_id.o: file_id.cc
 module.o: module.cc
 stabs_reader.o: stabs_reader.cc
+COVERAGE_SOURCES += stabs_reader.cc
+
+
+
+### Google C++ Testing Framework.
+VPATH += $(SRC)/testing/gtest/src
+GTEST_CPPFLAGS = -I$(SRC)/testing/gtest/include -I$(SRC)/testing/gtest
+gtest-all.o: gtest-all.cc
+gtest_main.o: gtest_main.cc
+gtest-all.o gtest_main.o: override CPPFLAGS += $(GTEST_CPPFLAGS)
+
+
+### Google C++ Mocking Framework.
+VPATH += $(SRC)/testing/src
+GMOCK_CPPFLAGS = -I$(SRC)/testing -I$(SRC)/testing/include
+gmock-all.o: gmock-all.cc
+gmock-all.o: override CPPFLAGS += $(GTEST_CPPFLAGS) $(GMOCK_CPPFLAGS)
+
+
+### Unit tests for google_breakpad::StabsReader.
+check: check-stabs_reader_unittest
+check-stabs_reader_unittest: stabs_reader_unittest
+stabs_reader_unittest:				\
+	gmock-all.o				\
+	gtest-all.o				\
+	gtest_main.o				\
+	stabs_reader.o				\
+	$(empty)
+CPP_EXECUTABLES += stabs_reader_unittest
+stabs_reader_unittest.o: stabs_reader_unittest.cc
+stabs_reader_unittest.o: override CPPFLAGS += $(GTEST_CPPFLAGS) \
+					      $(GMOCK_CPPFLAGS)
+clean::
+	rm -f stabs_reader_unittest
 
 
 ### Generic compilation rules.
-- 
cgit v1.2.1