You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by vi...@apache.org on 2014/05/22 02:55:12 UTC

[1/4] git commit: Added Owned::release().

Repository: mesos
Updated Branches:
  refs/heads/master 04abb408e -> ff08376c1


Added Owned::release().

Review: https://reviews.apache.org/r/21721


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/b327bc50
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/b327bc50
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/b327bc50

Branch: refs/heads/master
Commit: b327bc50cd9aaf5223e0a9a71af6bf988801b92f
Parents: 04abb40
Author: Vinod Kone <vi...@twitter.com>
Authored: Tue May 20 11:41:46 2014 -0700
Committer: Vinod Kone <vi...@twitter.com>
Committed: Wed May 21 17:55:02 2014 -0700

----------------------------------------------------------------------
 3rdparty/libprocess/include/process/owned.hpp | 30 +++++++++++++++++++++-
 3rdparty/libprocess/src/tests/owned_tests.cpp | 26 ++++++++++++++++---
 2 files changed, 52 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/b327bc50/3rdparty/libprocess/include/process/owned.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/owned.hpp b/3rdparty/libprocess/include/process/owned.hpp
index aacd7b8..ad6c734 100644
--- a/3rdparty/libprocess/include/process/owned.hpp
+++ b/3rdparty/libprocess/include/process/owned.hpp
@@ -40,6 +40,10 @@ public:
   // pointer will be reset after this function is invoked.
   Shared<T> share();
 
+  // Converts from an owned pointer to a raw pointer. This owned
+  // pointer will be reset after this function is invoked.
+  T* release();
+
 private:
   struct Data
   {
@@ -136,12 +140,16 @@ template <typename T>
 Shared<T> Owned<T>::share()
 {
   if (data.get() == NULL) {
+    // The ownership of this pointer has already been lost.
     return Shared<T>(NULL);
   }
 
   // Atomically set the pointer 'data->t' to NULL.
   T* t = __sync_fetch_and_and(&data->t, NULL);
-  CHECK(t != NULL) << "The ownership of this pointer has already been shared";
+  if (t == NULL) {
+    // The ownership of this pointer has already been lost.
+    return Shared<T>(NULL);
+  }
 
   data.reset();
   return Shared<T>(t);
@@ -149,6 +157,26 @@ Shared<T> Owned<T>::share()
 
 
 template <typename T>
+T* Owned<T>::release()
+{
+  if (data.get() == NULL) {
+    // The ownership of this pointer has already been lost.
+    return NULL;
+  }
+
+  // Atomically set the pointer 'data->t' to NULL.
+  T* t = __sync_fetch_and_and(&data->t, NULL);
+  if (t == NULL) {
+    // The ownership of this pointer has already been lost.
+    return NULL;
+  }
+
+  data.reset();
+  return t;
+}
+
+
+template <typename T>
 Owned<T>::Data::Data(T* _t)
   : t(CHECK_NOTNULL(_t)) {}
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/b327bc50/3rdparty/libprocess/src/tests/owned_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/tests/owned_tests.cpp b/3rdparty/libprocess/src/tests/owned_tests.cpp
index 234469b..50025d9 100644
--- a/3rdparty/libprocess/src/tests/owned_tests.cpp
+++ b/3rdparty/libprocess/src/tests/owned_tests.cpp
@@ -41,8 +41,8 @@ TEST(Owned, Null)
   Owned<Foo> owned;
   Owned<Foo> owned2(NULL);
 
-  EXPECT_TRUE(owned.get() == NULL);
-  EXPECT_TRUE(owned2.get() == NULL);
+  EXPECT_EQ(NULL, owned.get());
+  EXPECT_EQ(NULL, owned2.get());
 }
 
 
@@ -59,7 +59,7 @@ TEST(Owned, Share)
 
   Shared<Foo> shared = owned.share();
 
-  EXPECT_TRUE(owned.get() == NULL);
+  EXPECT_EQ(NULL, owned.get());
   EXPECT_TRUE(shared.unique());
 
   EXPECT_EQ(42, shared->get());
@@ -78,3 +78,23 @@ TEST(Owned, Share)
 
   EXPECT_TRUE(shared.unique());
 }
+
+
+TEST(Owned, Release)
+{
+  Foo* foo = new Foo();
+  foo->set(42);
+
+  Owned<Foo> owned(foo);
+
+  EXPECT_EQ(42, owned->get());
+  EXPECT_EQ(42, (*owned).get());
+  EXPECT_EQ(42, owned.get()->get());
+
+  Foo* raw = owned.release();
+  EXPECT_EQ(NULL, owned.get());
+  EXPECT_EQ(42, raw->get());
+
+  delete raw;
+  EXPECT_EQ(NULL, owned.get());
+}


[3/4] git commit: Fixed pointer ownership bug in Authorizer.

Posted by vi...@apache.org.
Fixed pointer ownership bug in Authorizer.

Review: https://reviews.apache.org/r/21727


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/ff08376c
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/ff08376c
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/ff08376c

Branch: refs/heads/master
Commit: ff08376c169700162d8c09c25c99e7f1a7a77e0f
Parents: 1f388d1
Author: Vinod Kone <vi...@twitter.com>
Authored: Tue May 20 12:32:33 2014 -0700
Committer: Vinod Kone <vi...@twitter.com>
Committed: Wed May 21 17:55:03 2014 -0700

----------------------------------------------------------------------
 include/mesos/mesos.proto     | 2 +-
 src/authorizer/authorizer.hpp | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/ff08376c/include/mesos/mesos.proto
----------------------------------------------------------------------
diff --git a/include/mesos/mesos.proto b/include/mesos/mesos.proto
index 8012873..ce780ca 100644
--- a/include/mesos/mesos.proto
+++ b/include/mesos/mesos.proto
@@ -644,7 +644,7 @@ message ACL {
  *
  */
 message ACLs {
-  required bool permissive = 1 [default = true];
+  optional bool permissive = 1 [default = true];
   repeated ACL.RunTasks run_tasks = 2;
   repeated ACL.ReceiveOffers receive_offers = 3;
   repeated ACL.HTTPGet http_get = 4;

http://git-wip-us.apache.org/repos/asf/mesos/blob/ff08376c/src/authorizer/authorizer.hpp
----------------------------------------------------------------------
diff --git a/src/authorizer/authorizer.hpp b/src/authorizer/authorizer.hpp
index 75b5a33..a8fde5a 100644
--- a/src/authorizer/authorizer.hpp
+++ b/src/authorizer/authorizer.hpp
@@ -287,7 +287,8 @@ Try<process::Owned<Authorizer> > Authorizer::create(const master::Flags& flags)
     return Error(authorizer.error());
   }
 
-  return authorizer.get().get();
+  process::Owned<LocalAuthorizer> authorizer_ = authorizer.get();
+  return static_cast<Authorizer*>(authorizer_.release());
 }
 
 


[2/4] git commit: Added TemporaryDirectoryTest fixture to stout tests.

Posted by vi...@apache.org.
Added TemporaryDirectoryTest fixture to stout tests.

Review: https://reviews.apache.org/r/21590


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/072bcdd7
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/072bcdd7
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/072bcdd7

Branch: refs/heads/master
Commit: 072bcdd731956abdfa16b23630f4d274c59654b5
Parents: b327bc5
Author: Vinod Kone <vi...@twitter.com>
Authored: Fri May 16 15:16:28 2014 -0700
Committer: Vinod Kone <vi...@twitter.com>
Committed: Wed May 21 17:55:03 2014 -0700

----------------------------------------------------------------------
 3rdparty/libprocess/3rdparty/stout/Makefile.am  |  1 +
 .../3rdparty/stout/tests/os/sendfile_tests.cpp  | 20 +++-------
 .../3rdparty/stout/tests/os_tests.cpp           | 27 ++++---------
 .../libprocess/3rdparty/stout/tests/utils.hpp   | 42 ++++++++++++++++++++
 src/tests/utils.hpp                             |  1 +
 5 files changed, 57 insertions(+), 34 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/072bcdd7/3rdparty/libprocess/3rdparty/stout/Makefile.am
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/3rdparty/stout/Makefile.am b/3rdparty/libprocess/3rdparty/stout/Makefile.am
index 81eaaa9..8f32a66 100644
--- a/3rdparty/libprocess/3rdparty/stout/Makefile.am
+++ b/3rdparty/libprocess/3rdparty/stout/Makefile.am
@@ -103,4 +103,5 @@ EXTRA_DIST =					\
   tests/some_tests.cpp				\
   tests/strings_tests.cpp			\
   tests/thread_tests.cpp			\
+  tests/utils.hpp				\
   tests/uuid_tests.cpp

http://git-wip-us.apache.org/repos/asf/mesos/blob/072bcdd7/3rdparty/libprocess/3rdparty/stout/tests/os/sendfile_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/3rdparty/stout/tests/os/sendfile_tests.cpp b/3rdparty/libprocess/3rdparty/stout/tests/os/sendfile_tests.cpp
index 194906e..4fe3f22 100644
--- a/3rdparty/libprocess/3rdparty/stout/tests/os/sendfile_tests.cpp
+++ b/3rdparty/libprocess/3rdparty/stout/tests/os/sendfile_tests.cpp
@@ -6,10 +6,11 @@
 #include <stout/os.hpp>
 #include <stout/path.hpp>
 
+#include <stout/tests/utils.hpp>
+
 using std::string;
 
-// TODO(bmahler): Extend from OsTest.
-class OsSendfileTest : public ::testing::Test
+class OsSendfileTest : public TemporaryDirectoryTest
 {
 public:
   OsSendfileTest()
@@ -25,24 +26,15 @@ public:
 protected:
   virtual void SetUp()
   {
-    const Try<string>& mkdtemp = os::mkdtemp();
-    ASSERT_SOME(mkdtemp);
-    tmpdir = mkdtemp.get();
-    filename = path::join(mkdtemp.get(), "lorem.txt");
+    TemporaryDirectoryTest::SetUp();
 
-    ASSERT_SOME(os::write(filename, LOREM_IPSUM));
-  }
+    filename = "lorem.txt";
 
-  virtual void TearDown()
-  {
-    ASSERT_SOME(os::rmdir(tmpdir));
+    ASSERT_SOME(os::write(filename, LOREM_IPSUM));
   }
 
   const string LOREM_IPSUM;
   string filename;
-
-private:
-  string tmpdir;
 };
 
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/072bcdd7/3rdparty/libprocess/3rdparty/stout/tests/os_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/3rdparty/stout/tests/os_tests.cpp b/3rdparty/libprocess/3rdparty/stout/tests/os_tests.cpp
index 76563c2..1a6ce0b 100644
--- a/3rdparty/libprocess/3rdparty/stout/tests/os_tests.cpp
+++ b/3rdparty/libprocess/3rdparty/stout/tests/os_tests.cpp
@@ -30,6 +30,8 @@
 #include <stout/os/sysctl.hpp>
 #endif
 
+#include <stout/tests/utils.hpp>
+
 using os::Exec;
 using os::Fork;
 using os::Process;
@@ -51,23 +53,7 @@ static hashset<string> listfiles(const string& directory)
 }
 
 
-class OsTest : public ::testing::Test
-{
-protected:
-  virtual void SetUp()
-  {
-    const Try<string>& mkdtemp = os::mkdtemp();
-    ASSERT_SOME(mkdtemp);
-    tmpdir = mkdtemp.get();
-  }
-
-  virtual void TearDown()
-  {
-    ASSERT_SOME(os::rmdir(tmpdir));
-  }
-
-  string tmpdir;
-};
+class OsTest : public TemporaryDirectoryTest {};
 
 
 TEST_F(OsTest, environment)
@@ -96,6 +82,7 @@ TEST_F(OsTest, environment)
 TEST_F(OsTest, rmdir)
 {
   const hashset<string> EMPTY;
+  const string& tmpdir = os::getcwd();
 
   hashset<string> expectedListing = EMPTY;
   EXPECT_EQ(expectedListing, listfiles(tmpdir));
@@ -170,7 +157,7 @@ TEST_F(OsTest, nonblock)
 
 TEST_F(OsTest, touch)
 {
-  const string& testfile  = tmpdir + "/" + UUID::random().toString();
+  const string& testfile  = path::join(os::getcwd(), UUID::random().toString());
 
   ASSERT_SOME(os::touch(testfile));
   ASSERT_TRUE(os::exists(testfile));
@@ -179,7 +166,7 @@ TEST_F(OsTest, touch)
 
 TEST_F(OsTest, readWriteString)
 {
-  const string& testfile  = tmpdir + "/" + UUID::random().toString();
+  const string& testfile  = path::join(os::getcwd(), UUID::random().toString());
   const string& teststr = "line1\nline2";
 
   ASSERT_SOME(os::write(testfile, teststr));
@@ -193,7 +180,7 @@ TEST_F(OsTest, readWriteString)
 
 TEST_F(OsTest, find)
 {
-  const string& testdir = tmpdir + "/" + UUID::random().toString();
+  const string& testdir = path::join(os::getcwd(), UUID::random().toString());
   const string& subdir = testdir + "/test1";
   ASSERT_SOME(os::mkdir(subdir)); // Create the directories.
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/072bcdd7/3rdparty/libprocess/3rdparty/stout/tests/utils.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/3rdparty/stout/tests/utils.hpp b/3rdparty/libprocess/3rdparty/stout/tests/utils.hpp
new file mode 100644
index 0000000..070bc6e
--- /dev/null
+++ b/3rdparty/libprocess/3rdparty/stout/tests/utils.hpp
@@ -0,0 +1,42 @@
+#include <gtest/gtest.h>
+
+#include <string>
+
+#include <stout/gtest.hpp>
+#include <stout/os.hpp>
+#include <stout/try.hpp>
+
+class TemporaryDirectoryTest : public ::testing::Test
+{
+protected:
+  virtual void SetUp()
+  {
+    // Save the current working directory.
+    cwd = os::getcwd();
+
+    // Create a temporary directory for the test.
+    Try<std::string> directory = os::mkdtemp();
+
+    ASSERT_SOME(directory) << "Failed to mkdtemp";
+
+    sandbox = directory.get();
+
+    // Run the test out of the temporary directory we created.
+    ASSERT_TRUE(os::chdir(sandbox.get()))
+      << "Failed to chdir into '" << sandbox.get() << "'";
+  }
+
+  virtual void TearDown()
+  {
+    // Return to previous working directory and cleanup the sandbox.
+    ASSERT_TRUE(os::chdir(cwd));
+
+    if (sandbox.isSome()) {
+      ASSERT_SOME(os::rmdir(sandbox.get()));
+    }
+  }
+
+private:
+  std::string cwd;
+  Option<std::string> sandbox;
+};

http://git-wip-us.apache.org/repos/asf/mesos/blob/072bcdd7/src/tests/utils.hpp
----------------------------------------------------------------------
diff --git a/src/tests/utils.hpp b/src/tests/utils.hpp
index 3b80933..5c86fd4 100644
--- a/src/tests/utils.hpp
+++ b/src/tests/utils.hpp
@@ -30,6 +30,7 @@ namespace internal {
 namespace tests {
 
 // Test fixture for creating a temporary directory for each test.
+// TODO(vinod): Fold this into stout/tests/utils.hpp.
 class TemporaryDirectoryTest : public ::testing::Test
 {
 protected:


[4/4] git commit: Added JSON support to flags.

Posted by vi...@apache.org.
Added JSON support to flags.

Review: https://reviews.apache.org/r/21565


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/1f388d18
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/1f388d18
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/1f388d18

Branch: refs/heads/master
Commit: 1f388d18ecc70f9d1275efb2a53efdb3ab9fa84d
Parents: 072bcdd
Author: Vinod Kone <vi...@twitter.com>
Authored: Fri May 16 10:45:58 2014 -0700
Committer: Vinod Kone <vi...@twitter.com>
Committed: Wed May 21 17:55:03 2014 -0700

----------------------------------------------------------------------
 .../stout/include/stout/flags/parse.hpp         | 31 ++++++
 .../3rdparty/stout/tests/flags_tests.cpp        | 99 +++++++++++++++++---
 2 files changed, 117 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/1f388d18/3rdparty/libprocess/3rdparty/stout/include/stout/flags/parse.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/3rdparty/stout/include/stout/flags/parse.hpp b/3rdparty/libprocess/3rdparty/stout/include/stout/flags/parse.hpp
index a781f14..d950a82 100644
--- a/3rdparty/libprocess/3rdparty/stout/include/stout/flags/parse.hpp
+++ b/3rdparty/libprocess/3rdparty/stout/include/stout/flags/parse.hpp
@@ -19,8 +19,12 @@
 
 #include <stout/duration.hpp>
 #include <stout/error.hpp>
+#include <stout/json.hpp>
+#include <stout/strings.hpp>
 #include <stout/try.hpp>
 
+#include <stout/os/read.hpp>
+
 namespace flags {
 
 template <typename T>
@@ -68,6 +72,33 @@ inline Try<Bytes> parse(const std::string& value)
   return Bytes::parse(value);
 }
 
+
+template <>
+inline Try<JSON::Object> parse(const std::string& value)
+{
+  // If the flag value corresponds to a file parse the contents of the
+  // file as JSON.
+  // TODO(vinod): We do not support relative paths because it is
+  // tricky to figure out if a flag value corresponds to a relative
+  // path or a JSON string. For example, "{", "  {" and "  \n {" are
+  // all valid prefixes of a JSON string.
+  if (strings::startsWith(value, "/") ||
+      strings::startsWith(value, "file://")) {
+
+    const std::string& path =
+      strings::remove(value, "file://", strings::PREFIX);
+
+    Try<std::string> read = os::read(path);
+    if (read.isError()) {
+      return Error("Error reading file '" + path + "': " + read.error());
+    }
+
+    return JSON::parse<JSON::Object>(read.get());
+  }
+
+  return JSON::parse<JSON::Object>(value);
+}
+
 } // namespace flags {
 
 #endif // __STOUT_FLAGS_PARSE_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/1f388d18/3rdparty/libprocess/3rdparty/stout/tests/flags_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/3rdparty/stout/tests/flags_tests.cpp b/3rdparty/libprocess/3rdparty/stout/tests/flags_tests.cpp
index ae6d9c1..3b60ff8 100644
--- a/3rdparty/libprocess/3rdparty/stout/tests/flags_tests.cpp
+++ b/3rdparty/libprocess/3rdparty/stout/tests/flags_tests.cpp
@@ -7,15 +7,21 @@
 #include <stout/flags.hpp>
 #include <stout/foreach.hpp>
 #include <stout/gtest.hpp>
+#include <stout/json.hpp>
 #include <stout/none.hpp>
 #include <stout/nothing.hpp>
 #include <stout/option.hpp>
 #include <stout/os.hpp>
+#include <stout/path.hpp>
 #include <stout/some.hpp>
 
+#include <stout/tests/utils.hpp>
 
 using namespace flags;
 
+using std::string;
+using std::map;
+
 class TestFlags : public virtual FlagsBase
 {
 public:
@@ -45,7 +51,7 @@ public:
         "Set name5");
   }
 
-  std::string name1;
+  string name1;
   int name2;
   bool name3;
   Option<bool> name4;
@@ -57,7 +63,7 @@ TEST(FlagsTest, Load)
 {
   TestFlags flags;
 
-  std::map<std::string, Option<std::string> > values;
+  map<string, Option<string> > values;
 
   values["name1"] = Some("billy joel");
   values["name2"] = Some("43");
@@ -81,7 +87,7 @@ TEST(FlagsTest, Add)
 {
   Flags<TestFlags> flags;
 
-  Option<std::string> name6;
+  Option<string> name6;
 
   flags.add(&name6,
             "name6",
@@ -94,13 +100,13 @@ TEST(FlagsTest, Add)
             "Also set name7",
             true);
 
-  Option<std::string> name8;
+  Option<string> name8;
 
   flags.add(&name8,
             "name8",
             "Also set name8");
 
-  std::map<std::string, Option<std::string> > values;
+  map<string, Option<string> > values;
 
   values["name6"] = Some("ben folds");
   values["no-name7"] = None();
@@ -120,7 +126,7 @@ TEST(FlagsTest, Flags)
 {
   TestFlags flags;
 
-  std::map<std::string, Option<std::string> > values;
+  map<string, Option<string> > values;
 
   values["name1"] = Some("billy joel");
   values["name2"] = Some("43");
@@ -325,7 +331,7 @@ TEST(FlagsTest, Stringification)
             "name8",
             "Optional name8");
 
-  std::map<std::string, Option<std::string> > values;
+  map<string, Option<string> > values;
 
   values["name2"] = Some("43");
   values["no-name4"] = None();
@@ -333,8 +339,8 @@ TEST(FlagsTest, Stringification)
 
   flags.load(values);
 
-  foreachpair(const std::string& name, const Flag& flag, flags) {
-    Option<std::string> value = flag.stringify(flags);
+  foreachpair (const string& name, const Flag& flag, flags) {
+    Option<string> value = flag.stringify(flags);
     if (name == "name1") {
       ASSERT_SOME(value);
       EXPECT_EQ("ben folds", value.get());
@@ -503,15 +509,82 @@ TEST(FlagsTest, Duration)
             "name7",
             "Also some amount of time");
 
-  std::map<std::string, Option<std::string> > values;
+  map<string, Option<string> > values;
 
   values["name6"] = Some("2mins");
   values["name7"] = Some("3hrs");
 
-  flags.load(values);
+  ASSERT_SOME(flags.load(values));
 
   EXPECT_EQ(Minutes(2), name6);
 
-  ASSERT_SOME(name7);
-  EXPECT_EQ(Hours(3), name7.get());
+  EXPECT_SOME_EQ(Hours(3), name7);
+}
+
+
+TEST(FlagsTest, JSON)
+{
+  Flags<TestFlags> flags;
+
+  Option<JSON::Object> json;
+
+  flags.add(&json,
+            "json",
+            "JSON string");
+
+  JSON::Object object;
+
+  object.values["strings"] = "string";
+  object.values["integer"] = 1;
+  object.values["double"] = -1.42;
+
+  JSON::Object nested;
+  nested.values["string"] = "string";
+
+  object.values["nested"] = nested;
+
+  map<string, Option<string> > values;
+  values["json"] = Some(stringify(object));
+
+  ASSERT_SOME(flags.load(values));
+
+  ASSERT_SOME_EQ(object, json);
+}
+
+
+class FlagsFileTest : public TemporaryDirectoryTest {};
+
+
+TEST_F(FlagsFileTest, JSONFile)
+{
+  Flags<TestFlags> flags;
+
+  Option<JSON::Object> json;
+
+  flags.add(&json,
+            "json",
+            "JSON string");
+
+  JSON::Object object;
+
+  object.values["strings"] = "string";
+  object.values["integer"] = 1;
+  object.values["double"] = -1.42;
+
+  JSON::Object nested;
+  nested.values["string"] = "string";
+
+  object.values["nested"] = nested;
+
+  // Write the JSON to a file.
+  const string& file = path::join(os::getcwd(), "file.json");
+  ASSERT_SOME(os::write(file, stringify(object)));
+
+  // Read the JSON from the file.
+  map<string, Option<string> > values;
+  values["json"] = Some(file);
+
+  ASSERT_SOME(flags.load(values));
+
+  ASSERT_SOME_EQ(object, json);
 }