提交 b9418fd0 authored 作者: Thomas Mueller's avatar Thomas Mueller

New file system to that checks a database is not corrupt after writing.

上级 b063c9cf
......@@ -9,6 +9,7 @@ package org.h2.test;
import java.sql.SQLException;
import java.util.Properties;
import org.h2.Driver;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
import org.h2.store.fs.FileSystemDisk;
import org.h2.test.bench.TestPerformance;
......@@ -122,10 +123,12 @@ import org.h2.test.unit.TestNetUtils;
import org.h2.test.unit.TestOldVersion;
import org.h2.test.unit.TestOverflow;
import org.h2.test.unit.TestPageStore;
import org.h2.test.unit.TestPageStoreCoverage;
import org.h2.test.unit.TestPattern;
import org.h2.test.unit.TestPgServer;
import org.h2.test.unit.TestReader;
import org.h2.test.unit.TestRecovery;
import org.h2.test.unit.TestReopen;
import org.h2.test.unit.TestSampleApps;
import org.h2.test.unit.TestScriptReader;
import org.h2.test.unit.TestSecurity;
......@@ -138,6 +141,7 @@ import org.h2.test.unit.TestValue;
import org.h2.test.unit.TestValueHashMap;
import org.h2.test.unit.TestValueMemory;
import org.h2.test.utils.OutputCatcher;
import org.h2.test.utils.RecordingFileSystem;
import org.h2.test.utils.SelfDestructor;
import org.h2.test.db.TestConnectionInfo;
import org.h2.tools.DeleteDbFiles;
......@@ -227,6 +231,11 @@ java org.h2.test.TestAll timer
*/
public boolean diskResult;
/**
* Test using the recording file system.
*/
public boolean record;
/**
* If the transaction log should be kept small (that is, the log should be
* switched early).
......@@ -291,14 +300,23 @@ java org.h2.test.TestAll timer
System.setProperty("h2.check2", "true");
int testing;
// System.setProperty("h2.lobInDatabase", "true");
// System.setProperty("h2.analyzeAuto", "100");
int testingRecovery;
System.setProperty("h2.delayWrongPasswordMin", "0");
System.setProperty("h2.check2", "false");
// System.setProperty("h2.pageSize", "64");
System.setProperty("h2.lobInDatabase", "true");
System.setProperty("h2.analyzeAuto", "100");
int testingRecovery2;
RecordingFileSystem.register();
// System.setProperty("h2.pageSize", "64");
/*
logFlush (& sync?) before writeBack
no commit in compact; logFlush - writeBack - switchLog
special file system with update log
test with small freeList pages, page size 64
power failure test
......@@ -306,6 +324,9 @@ power failure test: MULTI_THREADED=TRUE
power failure test: larger binaries and additional index.
power failure test with randomly generating / dropping indexes and tables.
lob in db: check for memory leaks (temp table with blob, then crash;
crash while truncating a table with a blob)
drop table test;
create table test(id identity, name varchar(100) default space(100));
@LOOP 10 insert into test select null, null from system_range(1, 100000);
......@@ -398,6 +419,16 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
* Run the tests with a number of different settings.
*/
private void runTests() throws SQLException {
int test;
//this.record=true;
if(record) {
System.setProperty("h2.delayWrongPasswordMin", "0");
RecordingFileSystem.register();
TestReopen reopen = new TestReopen();
RecordingFileSystem.setRecorder(reopen);
}
jdk14 = true;
smallLog = big = networked = memory = ssl = false;
diskResult = traceSystemOut = diskUndo = false;
......@@ -557,7 +588,6 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestCrashAPI().runTest(this);
new TestFuzzOptimizations().runTest(this);
new TestRandomSQL().runTest(this);
new TestKillRestart().runTest(this);
new TestKillRestartMulti().runTest(this);
new TestMultiThreaded().runTest(this);
......@@ -587,6 +617,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestMultiThreadedKernel().runTest(this);
new TestOverflow().runTest(this);
new TestPageStore().runTest(this);
new TestPageStoreCoverage().runTest(this);
new TestPattern().runTest(this);
new TestPgServer().runTest(this);
new TestReader().runTest(this);
......@@ -626,7 +657,8 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
*/
void beforeTest() throws SQLException {
Driver.load();
DeleteDbFiles.execute(TestBase.baseDir, null, true);
FileSystemDisk.getInstance().deleteRecursive(TestBase.BASE_TEST_DIR, false);
DeleteDbFiles.execute(TestBase.BASE_TEST_DIR, null, true);
FileSystemDisk.getInstance().deleteRecursive("trace.db", false);
if (networked) {
String[] args = ssl ? new String[] { "-tcpSSL", "true", "-tcpPort", "9192" } : new String[] { "-tcpPort",
......@@ -646,7 +678,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
if (networked && server != null) {
server.stop();
}
DeleteDbFiles.execute(TestBase.baseDir, null, true);
FileSystemDisk.getInstance().deleteRecursive(TestBase.BASE_TEST_DIR, false);
}
private void printSystem() {
......
......@@ -27,6 +27,7 @@ import org.h2.jdbc.JdbcConnection;
import org.h2.message.TraceSystem;
import org.h2.store.FileLock;
import org.h2.store.fs.FileSystem;
import org.h2.test.utils.RecordingFileSystem;
import org.h2.tools.DeleteDbFiles;
/**
......@@ -35,9 +36,9 @@ import org.h2.tools.DeleteDbFiles;
public abstract class TestBase {
/**
* The base directory to write test databases.
* The base directory.
*/
protected static String baseDir = getTestDir("");
public static final String BASE_TEST_DIR = "data";
/**
* The temporary directory.
......@@ -49,7 +50,10 @@ public abstract class TestBase {
*/
protected static int uniqueId;
private static final String BASE_TEST_DIR = "data";
/**
* The base directory to write test databases.
*/
private static String baseDir = getTestDir("");
/**
* The last time something was printed.
......@@ -192,6 +196,27 @@ public abstract class TestBase {
return getPassword("123");
}
/**
* Get the file translated file name.
* If a special file system is used, the prefix is prepended.
*
* @param name the original file name
* @return the translated file name
*/
protected String getBaseDir() {
if (config != null && config.record) {
int test;
// return "memFS:" + baseDir;
return RecordingFileSystem.PREFIX + "memFS:" + baseDir;
//return RecordingFileSystem.PREFIX + baseDir;
}
return baseDir;
}
/**
* Get the database URL for the given database name using the current
* configuration options.
......@@ -208,8 +233,10 @@ public abstract class TestBase {
if (config.memory) {
name = "mem:" + name;
} else {
if (!name.startsWith("memFS:") && !name.startsWith(baseDir + "/")) {
name = baseDir + "/" + name;
int idx = name.indexOf(':');
if (idx < 0 || idx > 10) {
// index > 10 if in options
name = getBaseDir() + "/" + name;
}
}
if (config.networked) {
......@@ -458,7 +485,7 @@ public abstract class TestBase {
* @param name the database name
*/
protected void deleteDb(String name) throws SQLException {
deleteDb(baseDir, name);
deleteDb(getBaseDir(), name);
}
/**
......
......@@ -12,13 +12,13 @@ import org.h2.store.fs.FileObject;
/**
* A debugging file that logs all operations.
*/
public class FileObjectDebug implements FileObject {
public class DebugFileObject implements FileObject {
private final FileSystemDebug fs;
private final DebugFileSystem fs;
private final FileObject file;
private final String name;
FileObjectDebug(FileSystemDebug fs, FileObject file) {
DebugFileObject(DebugFileSystem fs, FileObject file) {
this.fs = fs;
this.file = file;
this.name = file.getName();
......@@ -36,7 +36,7 @@ public class FileObjectDebug implements FileObject {
public String getName() {
debug("getName");
return FileSystemDebug.PREFIX + file.getName();
return DebugFileSystem.PREFIX + file.getName();
}
public long length() throws IOException {
......
......@@ -16,14 +16,14 @@ import org.h2.store.fs.FileSystem;
/**
* A debugging file system that logs all operations.
*/
public class FileSystemDebug extends FileSystem {
public class DebugFileSystem extends FileSystem {
/**
* The prefix used for a debugging file system.
*/
static final String PREFIX = "debug:";
private static final FileSystemDebug INSTANCE = new FileSystemDebug();
private static final DebugFileSystem INSTANCE = new DebugFileSystem();
private static final IOException POWER_OFF = new IOException("Simulated power failure");
......@@ -155,6 +155,12 @@ public class FileSystemDebug extends FileSystem {
return FileSystem.getInstance(fileName).isReadOnly(fileName);
}
public boolean setReadOnly(String fileName) {
fileName = translateFileName(fileName);
trace("setReadOnly", fileName);
return FileSystem.getInstance(fileName).setReadOnly(fileName);
}
public long length(String fileName) {
fileName = translateFileName(fileName);
trace("length", fileName);
......@@ -186,7 +192,7 @@ public class FileSystemDebug extends FileSystem {
public FileObject openFileObject(String fileName, String mode) throws IOException {
fileName = translateFileName(fileName);
trace("openFileObject", fileName, mode);
return new FileObjectDebug(this, FileSystem.getInstance(fileName).openFileObject(fileName, mode));
return new DebugFileObject(this, FileSystem.getInstance(fileName).openFileObject(fileName, mode));
}
public OutputStream openFileOutputStream(String fileName, boolean append) {
......@@ -199,7 +205,7 @@ public class FileSystemDebug extends FileSystem {
oldName = translateFileName(oldName);
newName = translateFileName(newName);
trace("rename", oldName, newName);
FileSystem.getInstance(oldName).copy(oldName, newName);
FileSystem.getInstance(oldName).rename(oldName, newName);
}
public boolean tryDelete(String fileName) {
......
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.utils;
/**
* A recorder for the recording file system.
*/
public interface Recorder {
/**
* Copy a file. The file name contains the source and the target file
* separated with a colon.
*/
int COPY = 3;
/**
* Create all parent directories.
*/
int CREATE_DIRS = 4;
/**
* Create a new file.
*/
int CREATE_NEW_FILE = 5;
/**
* Create a temporary file.
*/
int CREATE_TEMP_FILE = 6;
/**
* Delete a file.
*/
int DELETE = 7;
/**
* Delete all files and directories recursively.
*/
int DELETE_RECURSIVE = 8;
/**
* Open a file output stream.
*/
int OPEN_OUTPUT_STREAM = 9;
/**
* Rename a file. The file name contains the source and the target file
* separated with a colon.
*/
int RENAME = 10;
/**
* Set the length of the file.
*/
int SET_LENGTH = 1;
/**
* Try to delete the file.
*/
int TRY_DELETE = 2;
/**
* Write to the file.
*/
int WRITE = 0;
/**
* Record the method.
*
* @param op the operation
* @param fileName the file name or file name list
* @param data the data or null
* @param x the value or 0
*/
void log(int op, String fileName, byte[] data, long x);
}
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.utils;
import java.io.IOException;
import org.h2.store.fs.FileObject;
/**
* A file object that records all write operations and can re-play them.
*/
public class RecordingFileObject implements FileObject {
private final RecordingFileSystem fs;
private final FileObject file;
private final String name;
RecordingFileObject(RecordingFileSystem fs, FileObject file) {
this.fs = fs;
this.file = file;
this.name = file.getName();
}
public void close() throws IOException {
file.close();
}
public long getFilePointer() throws IOException {
return file.getFilePointer();
}
public String getName() {
return RecordingFileSystem.PREFIX + name;
}
public long length() throws IOException {
return file.length();
}
public void readFully(byte[] b, int off, int len) throws IOException {
file.readFully(b, off, len);
}
public void seek(long pos) throws IOException {
file.seek(pos);
}
public void setFileLength(long newLength) throws IOException {
fs.log(Recorder.SET_LENGTH, name, null, newLength);
file.setFileLength(newLength);
}
public void sync() throws IOException {
file.sync();
}
public void write(byte[] b, int off, int len) throws IOException {
byte[] buff = b;
if (off != 0 || len != b.length) {
buff = new byte[len];
System.arraycopy(b, off, buff, 0, len);
}
fs.log(Recorder.WRITE, name, buff, file.getFilePointer());
file.write(b, off, len);
}
}
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.h2.message.DbException;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileSystem;
/**
* A file system that records all write operations and can re-play them.
*/
public class RecordingFileSystem extends FileSystem {
/**
* The prefix used for a debugging file system.
*/
public static final String PREFIX = "rec:";
private static final RecordingFileSystem INSTANCE = new RecordingFileSystem();
private static Recorder recorder;
private boolean trace;
/**
* Register the file system.
*/
public static void register() {
FileSystem.register(INSTANCE);
}
/**
* Set the recorder class.
*
* @param recorder the recorder
*/
public static void setRecorder(Recorder recorder) {
RecordingFileSystem.recorder = recorder;
}
public boolean canWrite(String fileName) {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).canWrite(fileName);
}
public void copy(String original, String copy) {
original = translateFileName(original);
copy = translateFileName(copy);
log(Recorder.COPY, original + ":" + copy);
FileSystem.getInstance(original).copy(original, copy);
}
public void createDirs(String fileName) {
fileName = translateFileName(fileName);
log(Recorder.CREATE_DIRS, fileName);
FileSystem.getInstance(fileName).createDirs(fileName);
}
public boolean createNewFile(String fileName) {
fileName = translateFileName(fileName);
log(Recorder.CREATE_NEW_FILE, fileName);
return FileSystem.getInstance(fileName).createNewFile(fileName);
}
public String createTempFile(String prefix, String suffix, boolean deleteOnExit, boolean inTempDir)
throws IOException {
prefix = translateFileName(prefix);
log(Recorder.CREATE_TEMP_FILE, prefix + ":" + suffix + ":" + deleteOnExit + ":" + inTempDir);
return PREFIX + FileSystem.getInstance(prefix).createTempFile(prefix, suffix, deleteOnExit, inTempDir);
}
public void delete(String fileName) {
fileName = translateFileName(fileName);
log(Recorder.DELETE, fileName);
FileSystem.getInstance(fileName).delete(fileName);
}
public void deleteRecursive(String directory, boolean tryOnly) {
directory = translateFileName(directory);
log(Recorder.DELETE_RECURSIVE, directory);
FileSystem.getInstance(directory).deleteRecursive(directory, tryOnly);
}
public boolean exists(String fileName) {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).exists(fileName);
}
public boolean fileStartsWith(String fileName, String prefix) {
fileName = translateFileName(fileName);
prefix = translateFileName(prefix);
return FileSystem.getInstance(fileName).fileStartsWith(fileName, prefix);
}
public String getAbsolutePath(String fileName) {
fileName = translateFileName(fileName);
return PREFIX + FileSystem.getInstance(fileName).getAbsolutePath(fileName);
}
public String getFileName(String name) {
name = translateFileName(name);
return FileSystem.getInstance(name).getFileName(name);
}
public long getLastModified(String fileName) {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).getLastModified(fileName);
}
public String getParent(String fileName) {
fileName = translateFileName(fileName);
return PREFIX + FileSystem.getInstance(fileName).getParent(fileName);
}
public boolean isAbsolute(String fileName) {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).isAbsolute(fileName);
}
public boolean isDirectory(String fileName) {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).isDirectory(fileName);
}
public boolean isReadOnly(String fileName) {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).isReadOnly(fileName);
}
public boolean setReadOnly(String fileName) {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).setReadOnly(fileName);
}
public long length(String fileName) {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).length(fileName);
}
public String[] listFiles(String directory) {
directory = translateFileName(directory);
String[] list = FileSystem.getInstance(directory).listFiles(directory);
for (int i = 0; i < list.length; i++) {
list[i] = PREFIX + list[i];
}
return list;
}
public String normalize(String fileName) {
fileName = translateFileName(fileName);
return PREFIX + FileSystem.getInstance(fileName).normalize(fileName);
}
public InputStream openFileInputStream(String fileName) throws IOException {
fileName = translateFileName(fileName);
return FileSystem.getInstance(fileName).openFileInputStream(fileName);
}
public FileObject openFileObject(String fileName, String mode) throws IOException {
fileName = translateFileName(fileName);
return new RecordingFileObject(this, FileSystem.getInstance(fileName).openFileObject(fileName, mode));
}
public OutputStream openFileOutputStream(String fileName, boolean append) {
fileName = translateFileName(fileName);
log(Recorder.OPEN_OUTPUT_STREAM, fileName);
return FileSystem.getInstance(fileName).openFileOutputStream(fileName, append);
}
public void rename(String oldName, String newName) {
oldName = translateFileName(oldName);
newName = translateFileName(newName);
log(Recorder.RENAME, oldName + ":" + newName);
FileSystem.getInstance(oldName).rename(oldName, newName);
}
public boolean tryDelete(String fileName) {
fileName = translateFileName(fileName);
log(Recorder.TRY_DELETE, fileName);
return FileSystem.getInstance(fileName).tryDelete(fileName);
}
protected boolean accepts(String fileName) {
return fileName.startsWith(PREFIX);
}
private String translateFileName(String fileName) {
if (!fileName.startsWith(PREFIX)) {
DbException.throwInternalError(fileName + " doesn't start with " + PREFIX);
}
return fileName.substring(PREFIX.length());
}
public boolean isTrace() {
return trace;
}
public void setTrace(boolean trace) {
this.trace = trace;
}
public void log(int op, String fileName) {
log(op, fileName, null, 0);
}
public void log(int op, String fileName, byte[] data, long x) {
if (recorder != null) {
recorder.log(op, fileName, data, x);
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论