Skip to content

Commit

Permalink
[sql] Test mmap operation based on SQLite capabilities.
Browse files Browse the repository at this point in the history
The mmap mitigation for bug 537742 meant that the test wasn't running in
cases where mmap could potentially be enabled but wasn't enabled by
default.  Change the test to instead run when the platform allows SQLite
mmap to be enabled.

Add a test to verify that mmap cannot be enabled in cases where it is
expected not to work, so that platforms must make an explicit decision
about whether to allow mmap.

BUG=537742, 554269

Review URL: https://codereview.chromium.org/1529693002

Cr-Commit-Position: refs/heads/master@{#365904}
  • Loading branch information
dshess authored and Commit bot committed Dec 17, 2015
1 parent 55815ba commit 53adf16
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 80 deletions.
80 changes: 0 additions & 80 deletions sql/connection_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/metrics/statistics_recorder.h"
#include "base/strings/stringprintf.h"
#include "base/test/histogram_tester.h"
#include "base/trace_event/process_memory_dump.h"
#include "sql/connection.h"
Expand Down Expand Up @@ -1302,84 +1300,6 @@ TEST_F(SQLConnectionTest, TimeUpdateTransaction) {
EXPECT_EQ(0, samples->sum());
}

// Make sure that OS file writes to a mmap'ed file are reflected in the memory
// mapping of a memory-mapped file. Normally SQLite writes to memory-mapped
// files using memcpy(), which should stay consistent. Our SQLite is slightly
// patched to mmap read only, then write using OS file writes. If the
// memory-mapped version doesn't reflect the OS file writes, SQLite's
// memory-mapped I/O should be disabled on this platform.
#if !defined(MOJO_APPTEST_IMPL)
TEST_F(SQLConnectionTest, MmapTest) {
// Skip the test for platforms which don't enable memory-mapped I/O in SQLite,
// or which don't even support the pragma. The former seems to apply to iOS,
// the latter to older iOS.
// TODO(shess): Disable test on iOS? Disable on USE_SYSTEM_SQLITE?
{
sql::Statement s(db().GetUniqueStatement("PRAGMA mmap_size"));
if (!s.Step() || !s.ColumnInt64(0))
return;
}

// The test re-uses the database file to make sure it's representative of a
// SQLite file, but will be storing incompatible data.
db().Close();

const uint32 kFlags =
base::File::FLAG_OPEN|base::File::FLAG_READ|base::File::FLAG_WRITE;
char buf[4096];

// Create a file with a block of '0', a block of '1', and a block of '2'.
{
base::File f(db_path(), kFlags);
ASSERT_TRUE(f.IsValid());
memset(buf, '0', sizeof(buf));
ASSERT_EQ(f.Write(0*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));

memset(buf, '1', sizeof(buf));
ASSERT_EQ(f.Write(1*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));

memset(buf, '2', sizeof(buf));
ASSERT_EQ(f.Write(2*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));
}

// mmap the file and verify that everything looks right.
{
base::MemoryMappedFile m;
ASSERT_TRUE(m.Initialize(db_path()));

memset(buf, '0', sizeof(buf));
ASSERT_EQ(0, memcmp(buf, m.data() + 0*sizeof(buf), sizeof(buf)));

memset(buf, '1', sizeof(buf));
ASSERT_EQ(0, memcmp(buf, m.data() + 1*sizeof(buf), sizeof(buf)));

memset(buf, '2', sizeof(buf));
ASSERT_EQ(0, memcmp(buf, m.data() + 2*sizeof(buf), sizeof(buf)));

// Scribble some '3' into the first page of the file, and verify that it
// looks the same in the memory mapping.
{
base::File f(db_path(), kFlags);
ASSERT_TRUE(f.IsValid());
memset(buf, '3', sizeof(buf));
ASSERT_EQ(f.Write(0*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));
}
ASSERT_EQ(0, memcmp(buf, m.data() + 0*sizeof(buf), sizeof(buf)));

// Repeat with a single '4' in case page-sized blocks are different.
const size_t kOffset = 1*sizeof(buf) + 123;
ASSERT_NE('4', m.data()[kOffset]);
{
base::File f(db_path(), kFlags);
ASSERT_TRUE(f.IsValid());
buf[0] = '4';
ASSERT_EQ(f.Write(kOffset, buf, 1), 1);
}
ASSERT_EQ('4', m.data()[kOffset]);
}
}
#endif

TEST_F(SQLConnectionTest, OnMemoryDump) {
base::trace_event::ProcessMemoryDump pmd(nullptr);
base::trace_event::MemoryDumpArgs args = {
Expand Down
104 changes: 104 additions & 0 deletions sql/sqlite_features_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "sql/connection.h"
#include "sql/statement.h"
Expand Down Expand Up @@ -143,4 +144,107 @@ TEST_F(SQLiteFeaturesTest, ForeignKeySupport) {
EXPECT_EQ(0u, rows);
}

#if defined(MOJO_APPTEST_IMPL) || defined(OS_IOS)
// If the platform cannot support SQLite mmap'ed I/O, make sure SQLite isn't
// offering to support it.
TEST_F(SQLiteFeaturesTest, NoMmap) {
// For recent versions of SQLite, SQLITE_MAX_MMAP_SIZE=0 can be used to
// disable mmap support. Alternately, sqlite3_config() could be used. In
// that case, the pragma will run successfully, but the size will always be 0.
//
// The SQLite embedded in older iOS releases predates the addition of mmap
// support. In that case the pragma will run without error, but no results
// are returned when querying the value.
//
// MojoVFS implements a no-op for xFileControl(). PRAGMA mmap_size is
// implemented in terms of SQLITE_FCNTL_MMAP_SIZE. In that case, the pragma
// will succeed but with no effect.
ignore_result(db().Execute("PRAGMA mmap_size = 1048576"));
sql::Statement s(db().GetUniqueStatement("PRAGMA mmap_size"));
ASSERT_TRUE(!s.Step() || !s.ColumnInt64(0));
}
#else
// Verify that OS file writes are reflected in the memory mapping of a
// memory-mapped file. Normally SQLite writes to memory-mapped files using
// memcpy(), which should stay consistent. Our SQLite is slightly patched to
// mmap read only, then write using OS file writes. If the memory-mapped
// version doesn't reflect the OS file writes, SQLite's memory-mapped I/O should
// be disabled on this platform using SQLITE_MAX_MMAP_SIZE=0.
TEST_F(SQLiteFeaturesTest, Mmap) {
// Try to turn on mmap'ed I/O.
ignore_result(db().Execute("PRAGMA mmap_size = 1048576"));
{
sql::Statement s(db().GetUniqueStatement("PRAGMA mmap_size"));

#if !defined(USE_SYSTEM_SQLITE)
// With Chromium's version of SQLite, the setting should always be non-zero.
ASSERT_TRUE(s.Step());
ASSERT_GT(s.ColumnInt64(0), 0);
#else
// With the system SQLite, don't verify underlying mmap functionality if the
// SQLite is too old to support mmap, or if mmap is disabled (see NoMmap
// test). USE_SYSTEM_SQLITE is not bundled into the NoMmap case because
// whether mmap is enabled or not is outside of Chromium's control.
if (!s.Step() || !s.ColumnInt64(0))
return;
#endif
}
db().Close();

const uint32 kFlags =
base::File::FLAG_OPEN|base::File::FLAG_READ|base::File::FLAG_WRITE;
char buf[4096];

// Create a file with a block of '0', a block of '1', and a block of '2'.
{
base::File f(db_path(), kFlags);
ASSERT_TRUE(f.IsValid());
memset(buf, '0', sizeof(buf));
ASSERT_EQ(f.Write(0*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));

memset(buf, '1', sizeof(buf));
ASSERT_EQ(f.Write(1*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));

memset(buf, '2', sizeof(buf));
ASSERT_EQ(f.Write(2*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));
}

// mmap the file and verify that everything looks right.
{
base::MemoryMappedFile m;
ASSERT_TRUE(m.Initialize(db_path()));

memset(buf, '0', sizeof(buf));
ASSERT_EQ(0, memcmp(buf, m.data() + 0*sizeof(buf), sizeof(buf)));

memset(buf, '1', sizeof(buf));
ASSERT_EQ(0, memcmp(buf, m.data() + 1*sizeof(buf), sizeof(buf)));

memset(buf, '2', sizeof(buf));
ASSERT_EQ(0, memcmp(buf, m.data() + 2*sizeof(buf), sizeof(buf)));

// Scribble some '3' into the first page of the file, and verify that it
// looks the same in the memory mapping.
{
base::File f(db_path(), kFlags);
ASSERT_TRUE(f.IsValid());
memset(buf, '3', sizeof(buf));
ASSERT_EQ(f.Write(0*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));
}
ASSERT_EQ(0, memcmp(buf, m.data() + 0*sizeof(buf), sizeof(buf)));

// Repeat with a single '4' in case page-sized blocks are different.
const size_t kOffset = 1*sizeof(buf) + 123;
ASSERT_NE('4', m.data()[kOffset]);
{
base::File f(db_path(), kFlags);
ASSERT_TRUE(f.IsValid());
buf[0] = '4';
ASSERT_EQ(f.Write(kOffset, buf, 1), 1);
}
ASSERT_EQ('4', m.data()[kOffset]);
}
}
#endif

} // namespace

0 comments on commit 53adf16

Please sign in to comment.