Add metrics library tests. Some metrics daemon API cleanup.

Refactor the metrics daemon API a little so that we don't need
to link in libmetrics into the daemon test binary.

Review URL: http://codereview.chromium.org/2079007
diff --git a/metrics/Makefile b/metrics/Makefile
index 5ce0caf..e0c3741 100644
--- a/metrics/Makefile
+++ b/metrics/Makefile
@@ -15,11 +15,15 @@
 DAEMON_TEST = metrics_daemon_test
 LIB = libmetrics.a
 SHAREDLIB = libmetrics.so
+LIB_TEST = metrics_library_test
 
 CLIENT_OBJS = \
 	metrics_client.o
 LIB_OBJS = \
 	metrics_library.o
+TESTLIB_OBJS = \
+	metrics_library.o \
+	metrics_library_test.o
 DAEMON_OBJS = \
 	metrics_daemon.o \
 	metrics_daemon_main.o
@@ -27,12 +31,13 @@
 	metrics_daemon.o \
 	metrics_daemon_test.o
 
-DAEMON_LDFLAGS = $(LDCONFIG) -lrt -lbase -lpthread -lgflags
+DAEMON_LDFLAGS = $(LDFLAGS) $(LDCONFIG) -lrt -lbase -lpthread -lgflags
 TESTDAEMON_LIBS = -lgmock -lgtest
+TESTLIB_LIBS = -lgtest -lbase -lrt -lpthread
 
 all: $(LIB) $(SHAREDLIB) $(CLIENT) $(DAEMON)
 
-tests: $(DAEMON_TEST)
+tests: $(DAEMON_TEST) $(LIB_TEST)
 
 $(CLIENT): $(CLIENT_OBJS) $(SHAREDLIB)
 	$(CXX) $(LDFLAGS) $^ -o $@
@@ -40,7 +45,7 @@
 $(DAEMON): $(DAEMON_OBJS) $(SHAREDLIB)
 	$(CXX) -o $@ $^ $(DAEMON_LDFLAGS)
 
-$(DAEMON_TEST): $(TESTDAEMON_OBJS) $(SHAREDLIB)
+$(DAEMON_TEST): $(TESTDAEMON_OBJS)
 	$(CXX) -o $@ $^ $(DAEMON_LDFLAGS) $(TESTDAEMON_LIBS)
 
 $(LIB): $(LIB_OBJS)
@@ -49,6 +54,9 @@
 $(SHAREDLIB): $(LIB_OBJS)
 	$(CXX) $(LDFLAGS) -shared $^ -o $@
 
+$(LIB_TEST): $(TESTLIB_OBJS) $(SHAREDLIB)
+	$(CXX) -o $@ $^ $(LDFLAGS) $(TESTLIB_LIBS)
+
 %.o: %.cc
 	$(CXX) $(CXXFLAGS) -c $< -o $@
 
@@ -64,4 +72,5 @@
 	power_states.h
 
 clean:
-	rm -f $(CLIENT) $(DAEMON) $(LIB) $(SHAREDLIB) $(TESTDAEMON) *.o
+	rm -f $(CLIENT) $(DAEMON) $(LIB) $(SHAREDLIB) *.o
+	rm -f $(DAEMON_TEST) $(LIB_TEST)
diff --git a/metrics/metrics_daemon.cc b/metrics/metrics_daemon.cc
index a66b570..6e01932 100644
--- a/metrics/metrics_daemon.cc
+++ b/metrics/metrics_daemon.cc
@@ -100,9 +100,6 @@
 };
 
 void MetricsDaemon::Run(bool run_as_daemon) {
-  MetricsLibrary metrics_lib;
-  metrics_lib.Init();
-  Init(false, &metrics_lib);
   if (!run_as_daemon || daemon(0, 0) == 0) {
     Loop();
   }
@@ -223,10 +220,10 @@
       network_state_ == kNetworkStateOnline &&
       power_state_ != kPowerStateMem) {
     int online_time = static_cast<int>(now - network_state_last_);
-    PublishMetric(kMetricTimeToNetworkDropName, online_time,
-                  kMetricTimeToNetworkDropMin,
-                  kMetricTimeToNetworkDropMax,
-                  kMetricTimeToNetworkDropBuckets);
+    SendMetric(kMetricTimeToNetworkDropName, online_time,
+               kMetricTimeToNetworkDropMin,
+               kMetricTimeToNetworkDropMax,
+               kMetricTimeToNetworkDropBuckets);
   }
 
   network_state_ = state;
@@ -349,10 +346,10 @@
       // the usage to the nearest minute and sends it to UMA.
       int minutes =
           (record.seconds_ + kSecondsPerMinute / 2) / kSecondsPerMinute;
-      PublishMetric(kMetricDailyUseTimeName, minutes,
-                    kMetricDailyUseTimeMin,
-                    kMetricDailyUseTimeMax,
-                    kMetricDailyUseTimeBuckets);
+      SendMetric(kMetricDailyUseTimeName, minutes,
+                 kMetricDailyUseTimeMin,
+                 kMetricDailyUseTimeMax,
+                 kMetricDailyUseTimeBuckets);
 
       // Truncates the usage file to ensure that no duplicate usage is
       // sent to UMA.
@@ -445,8 +442,8 @@
   usemon_interval_ = 0;
 }
 
-void MetricsDaemon::PublishMetric(const char* name, int sample,
-                                  int min, int max, int nbuckets) {
+void MetricsDaemon::SendMetric(const std::string& name, int sample,
+                               int min, int max, int nbuckets) {
   DLOG(INFO) << "received metric: " << name << " " << sample << " "
              << min << " " << max << " " << nbuckets;
   metrics_lib_->SendToUMA(name, sample, min, max, nbuckets);
diff --git a/metrics/metrics_daemon.h b/metrics/metrics_daemon.h
index 3aeaaaa..ba9e13c 100644
--- a/metrics/metrics_daemon.h
+++ b/metrics/metrics_daemon.h
@@ -30,6 +30,9 @@
         usemon_source_(NULL) {}
   ~MetricsDaemon() {}
 
+  // Initializes.
+  void Init(bool testing, MetricsLibraryInterface* metrics_lib);
+
   // Does all the work. If |run_as_daemon| is true, daemonizes by
   // forking.
   void Run(bool run_as_daemon);
@@ -48,8 +51,8 @@
   FRIEND_TEST(MetricsDaemonTest, NetStateChangedSimpleDrop);
   FRIEND_TEST(MetricsDaemonTest, NetStateChangedSuspend);
   FRIEND_TEST(MetricsDaemonTest, PowerStateChanged);
-  FRIEND_TEST(MetricsDaemonTest, PublishMetric);
   FRIEND_TEST(MetricsDaemonTest, ScreenSaverStateChanged);
+  FRIEND_TEST(MetricsDaemonTest, SendMetric);
   FRIEND_TEST(MetricsDaemonTest, SessionStateChanged);
   FRIEND_TEST(MetricsDaemonTest, SetUserActiveStateSendOnLogin);
   FRIEND_TEST(MetricsDaemonTest, SetUserActiveStateSendOnMonitor);
@@ -119,9 +122,6 @@
   // Array of user session states.
   static const char* kSessionStates_[kNumberSessionStates];
 
-  // Initializes.
-  void Init(bool testing, MetricsLibraryInterface* metrics_lib);
-
   // Creates the event loop and enters it.
   void Loop();
 
@@ -188,11 +188,11 @@
   // Unschedules a scheduled use monitor, if any.
   void UnscheduleUseMonitor();
 
-  // Sends a stat to Chrome for transport to UMA (or prints it for
-  // testing). See MetricsLibrary::SendToChrome in metrics_library.h
-  // for a description of the arguments.
-  void PublishMetric(const char* name, int sample,
-                     int min, int max, int nbuckets);
+  // Sends a regular (exponential) histogram sample to Chrome for
+  // transport to UMA. See MetricsLibrary::SendToUMA in
+  // metrics_library.h for a description of the arguments.
+  void SendMetric(const std::string& name, int sample,
+                  int min, int max, int nbuckets);
 
   // Test mode.
   bool testing_;
diff --git a/metrics/metrics_daemon_main.cc b/metrics/metrics_daemon_main.cc
index 7ace333..f3f0714 100644
--- a/metrics/metrics_daemon_main.cc
+++ b/metrics/metrics_daemon_main.cc
@@ -10,7 +10,10 @@
 DEFINE_bool(daemon, true, "run as daemon (use -nodaemon for debugging)");
 
 int main(int argc, char** argv) {
-  MetricsDaemon::MetricsDaemon d;
   google::ParseCommandLineFlags(&argc, &argv, true);
-  d.Run(FLAGS_daemon);
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+  MetricsDaemon daemon;
+  daemon.Init(false, &metrics_lib);
+  daemon.Run(FLAGS_daemon);
 }
diff --git a/metrics/metrics_daemon_test.cc b/metrics/metrics_daemon_test.cc
index 8180b14..2edbeda 100644
--- a/metrics/metrics_daemon_test.cc
+++ b/metrics/metrics_daemon_test.cc
@@ -25,7 +25,9 @@
 class MetricsDaemonTest : public testing::Test {
  protected:
   virtual void SetUp() {
+    EXPECT_EQ(NULL, daemon_.daily_use_record_file_);
     daemon_.Init(true, &metrics_lib_);
+    EXPECT_TRUE(NULL != daemon_.daily_use_record_file_);
     daemon_.daily_use_record_file_ = kTestDailyUseRecordFile;
 
     // The test fixture object will be used by the log message handler.
@@ -423,12 +425,6 @@
   EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 7, /* seconds */ 30);
 }
 
-TEST_F(MetricsDaemonTest, PublishMetric) {
-  ExpectMetric("Dummy.Metric", 3, 1, 100, 50);
-  daemon_.PublishMetric("Dummy.Metric", /* sample */ 3,
-                        /* min */ 1, /* max */ 100, /* buckets */ 50);
-}
-
 TEST_F(MetricsDaemonTest, ScreenSaverStateChanged) {
   EXPECT_EQ(MetricsDaemon::kUnknownScreenSaverState,
             daemon_.screensaver_state_);
@@ -460,6 +456,12 @@
   EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 5, /* seconds */ 200);
 }
 
+TEST_F(MetricsDaemonTest, SendMetric) {
+  ExpectMetric("Dummy.Metric", 3, 1, 100, 50);
+  daemon_.SendMetric("Dummy.Metric", /* sample */ 3,
+                     /* min */ 1, /* max */ 100, /* buckets */ 50);
+}
+
 TEST_F(MetricsDaemonTest, SessionStateChanged) {
   EXPECT_EQ(MetricsDaemon::kUnknownSessionState, daemon_.session_state_);
   EXPECT_FALSE(daemon_.user_active_);
diff --git a/metrics/metrics_library.cc b/metrics/metrics_library.cc
index 98415a8..6ec7226 100644
--- a/metrics/metrics_library.cc
+++ b/metrics/metrics_library.cc
@@ -16,16 +16,16 @@
 
 static const char kAutotestPath[] =
     "/var/log/metrics/autotest-events";
-static const char kChromePath[] =
+static const char kUMAEventsPath[] =
     "/var/log/metrics/uma-events";
 static const int32_t kBufferSize = 1024;
 
 using namespace std;
 
 // TODO(sosa@chromium.org) - use Chromium logger instead of stderr
-static void PrintError(const char *message, const char *file,
+static void PrintError(const char* message, const char* file,
                        int code) {
-  const char *kProgramName = "metrics_library";
+  static const char *kProgramName = "libmetrics";
   if (code == 0) {
     fprintf(stderr, "%s: %s\n", kProgramName, message);
   } else if (file == NULL) {
@@ -37,14 +37,16 @@
   }
 }
 
-// Sends message of size |length| to Chrome and returns true on success.
-static bool SendMessageToChrome(int32_t length, const char *message) {
-  int chrome_fd = open(kChromePath,
+MetricsLibrary::MetricsLibrary()
+    : uma_events_file_(NULL) {}
+
+bool MetricsLibrary::SendMessageToChrome(int32_t length, const char* message) {
+  int chrome_fd = open(uma_events_file_,
                        O_WRONLY | O_APPEND | O_CREAT,
                        READ_WRITE_ALL_FILE_FLAGS);
   // If we failed to open it, return.
   if (chrome_fd < 0) {
-    PrintError("open", kChromePath, errno);
+    PrintError("open", uma_events_file_, errno);
     return false;
   }
 
@@ -56,36 +58,28 @@
   // Grab an exclusive lock to protect Chrome from truncating
   // underneath us. Keep the file locked as briefly as possible.
   if (flock(chrome_fd, LOCK_EX) < 0) {
-    PrintError("flock", kChromePath, errno);
+    PrintError("flock", uma_events_file_, errno);
     close(chrome_fd);
     return false;
   }
 
   bool success = true;
   if (write(chrome_fd, message, length) != length) {
-    PrintError("write", kChromePath, errno);
+    PrintError("write", uma_events_file_, errno);
     success = false;
   }
 
   // Release the file lock and close file.
   if (flock(chrome_fd, LOCK_UN) < 0) {
-    PrintError("unlock", kChromePath, errno);
+    PrintError("unlock", uma_events_file_, errno);
     success = false;
   }
   close(chrome_fd);
   return success;
 }
 
-// Formats a name/value message for Chrome in |buffer| and returns the
-// length of the message or a negative value on error.
-//
-// Message format is: | LENGTH(binary) | NAME | \0 | VALUE | \0 |
-//
-// The arbitrary |format| argument covers the non-LENGTH portion of the
-// message. The caller is responsible to store the \0 character
-// between NAME and VALUE (e.g. "%s%c%d", name, '\0', value).
-static int32_t FormatChromeMessage(int32_t buffer_size, char *buffer,
-                                   const char *format, ...) {
+int32_t MetricsLibrary::FormatChromeMessage(int32_t buffer_size, char* buffer,
+                                            const char* format, ...) {
   int32_t message_length;
   size_t len_size = sizeof(message_length);
 
@@ -115,9 +109,9 @@
 }
 
 void MetricsLibrary::Init() {
+  uma_events_file_ = kUMAEventsPath;
 }
 
-// static
 bool MetricsLibrary::SendToAutotest(const string& name, int value) {
   FILE *autotest_file = fopen(kAutotestPath, "a+");
   if (autotest_file == NULL) {
diff --git a/metrics/metrics_library.h b/metrics/metrics_library.h
index 2a6412c..8ae3f5b 100644
--- a/metrics/metrics_library.h
+++ b/metrics/metrics_library.h
@@ -5,9 +5,10 @@
 #ifndef METRICS_LIBRARY_H_
 #define METRICS_LIBRARY_H_
 
+#include <sys/types.h>
 #include <string>
 
-// TODO(sosa@chromium.org): Add testing for send methods
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 class MetricsLibraryInterface {
  public:
@@ -21,6 +22,8 @@
 // Library used to send metrics to both Autotest and Chrome/UMA.
 class MetricsLibrary : public MetricsLibraryInterface {
  public:
+  MetricsLibrary();
+
   // Initializes the library.
   void Init();
 
@@ -51,6 +54,30 @@
 
   // Sends to Autotest and returns true on success.
   static bool SendToAutotest(const std::string& name, int value);
+
+ private:
+  friend class MetricsLibraryTest;
+  FRIEND_TEST(MetricsLibraryTest, FormatChromeMessage);
+  FRIEND_TEST(MetricsLibraryTest, FormatChromeMessageTooLong);
+  FRIEND_TEST(MetricsLibraryTest, SendMessageToChrome);
+  FRIEND_TEST(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation);
+
+  // Sends message of size |length| to Chrome for transport to UMA and
+  // returns true on success.
+  bool SendMessageToChrome(int32_t length, const char* message);
+
+  // Formats a name/value message for Chrome in |buffer| and returns the
+  // length of the message or a negative value on error.
+  //
+  // Message format is: | LENGTH(binary) | NAME | \0 | VALUE | \0 |
+  //
+  // The arbitrary |format| argument covers the non-LENGTH portion of the
+  // message. The caller is responsible to store the \0 character
+  // between NAME and VALUE (e.g. "%s%c%d", name, '\0', value).
+  int32_t FormatChromeMessage(int32_t buffer_size, char* buffer,
+                              const char *format, ...);
+
+  const char* uma_events_file_;
 };
 
 #endif /* METRICS_LIBRARY_H_ */
diff --git a/metrics/metrics_library_test.cc b/metrics/metrics_library_test.cc
new file mode 100644
index 0000000..f585d35
--- /dev/null
+++ b/metrics/metrics_library_test.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics_library.h"
+
+#include <cstring>
+
+#include <base/file_util.h>
+#include <gtest/gtest.h>
+
+static const FilePath kTestUMAEventsFile("test-uma-events");
+
+class MetricsLibraryTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    EXPECT_EQ(NULL, lib_.uma_events_file_);
+    lib_.Init();
+    EXPECT_TRUE(NULL != lib_.uma_events_file_);
+    lib_.uma_events_file_ = kTestUMAEventsFile.value().c_str();
+  }
+
+  virtual void TearDown() {
+    file_util::Delete(kTestUMAEventsFile, false);
+  }
+
+  MetricsLibrary lib_;
+};
+
+TEST_F(MetricsLibraryTest, FormatChromeMessage) {
+  char buf[7];
+  const int kLen = 6;
+  EXPECT_EQ(kLen, lib_.FormatChromeMessage(7, buf, "%d", 1));
+
+  char exp[kLen];
+  sprintf(exp, "%c%c%c%c1", kLen, 0, 0, 0);
+  EXPECT_EQ(0, memcmp(exp, buf, kLen));
+}
+
+TEST_F(MetricsLibraryTest, FormatChromeMessageTooLong) {
+  char buf[7];
+  EXPECT_EQ(-1, lib_.FormatChromeMessage(7, buf, "test"));
+}
+
+TEST_F(MetricsLibraryTest, SendEnumToUMA) {
+  char buf[100];
+  const int kLen = 40;
+  EXPECT_TRUE(lib_.SendEnumToUMA("Test.EnumMetric", 1, 3));
+  EXPECT_EQ(kLen, file_util::ReadFile(kTestUMAEventsFile, buf, 100));
+
+  char exp[kLen];
+  sprintf(exp, "%c%c%c%clinearhistogram%cTest.EnumMetric 1 3",
+          kLen, 0, 0, 0, 0);
+  EXPECT_EQ(0, memcmp(exp, buf, kLen));
+}
+
+TEST_F(MetricsLibraryTest, SendMessageToChrome) {
+  EXPECT_TRUE(lib_.SendMessageToChrome(4, "test"));
+  EXPECT_TRUE(lib_.SendMessageToChrome(7, "content"));
+  std::string uma_events;
+  EXPECT_TRUE(file_util::ReadFileToString(kTestUMAEventsFile, &uma_events));
+  EXPECT_EQ("testcontent", uma_events);
+}
+
+TEST_F(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation) {
+  // Checks that the library doesn't die badly if the file can't be
+  // created.
+  static const char kDoesNotExistFile[] = "/does/not/exist";
+  lib_.uma_events_file_ = kDoesNotExistFile;
+  static const char kDummyMessage[] = "Dummy Message";
+  EXPECT_FALSE(lib_.SendMessageToChrome(strlen(kDummyMessage), kDummyMessage));
+  file_util::Delete(FilePath(kDoesNotExistFile), false);
+}
+
+TEST_F(MetricsLibraryTest, SendToUMA) {
+  char buf[100];
+  const int kLen = 37;
+  EXPECT_TRUE(lib_.SendToUMA("Test.Metric", 2, 1, 100, 50));
+  EXPECT_EQ(kLen, file_util::ReadFile(kTestUMAEventsFile, buf, 100));
+
+  char exp[kLen];
+  sprintf(exp, "%c%c%c%chistogram%cTest.Metric 2 1 100 50", kLen, 0, 0, 0, 0);
+  EXPECT_EQ(0, memcmp(exp, buf, kLen));
+}
+
+int main(int argc, char** argv) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}