提交 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; ...@@ -9,6 +9,7 @@ package org.h2.test;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties; import java.util.Properties;
import org.h2.Driver; import org.h2.Driver;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.store.fs.FileSystemDisk; import org.h2.store.fs.FileSystemDisk;
import org.h2.test.bench.TestPerformance; import org.h2.test.bench.TestPerformance;
...@@ -122,10 +123,12 @@ import org.h2.test.unit.TestNetUtils; ...@@ -122,10 +123,12 @@ import org.h2.test.unit.TestNetUtils;
import org.h2.test.unit.TestOldVersion; import org.h2.test.unit.TestOldVersion;
import org.h2.test.unit.TestOverflow; import org.h2.test.unit.TestOverflow;
import org.h2.test.unit.TestPageStore; import org.h2.test.unit.TestPageStore;
import org.h2.test.unit.TestPageStoreCoverage;
import org.h2.test.unit.TestPattern; import org.h2.test.unit.TestPattern;
import org.h2.test.unit.TestPgServer; import org.h2.test.unit.TestPgServer;
import org.h2.test.unit.TestReader; import org.h2.test.unit.TestReader;
import org.h2.test.unit.TestRecovery; import org.h2.test.unit.TestRecovery;
import org.h2.test.unit.TestReopen;
import org.h2.test.unit.TestSampleApps; import org.h2.test.unit.TestSampleApps;
import org.h2.test.unit.TestScriptReader; import org.h2.test.unit.TestScriptReader;
import org.h2.test.unit.TestSecurity; import org.h2.test.unit.TestSecurity;
...@@ -138,6 +141,7 @@ import org.h2.test.unit.TestValue; ...@@ -138,6 +141,7 @@ import org.h2.test.unit.TestValue;
import org.h2.test.unit.TestValueHashMap; import org.h2.test.unit.TestValueHashMap;
import org.h2.test.unit.TestValueMemory; import org.h2.test.unit.TestValueMemory;
import org.h2.test.utils.OutputCatcher; import org.h2.test.utils.OutputCatcher;
import org.h2.test.utils.RecordingFileSystem;
import org.h2.test.utils.SelfDestructor; import org.h2.test.utils.SelfDestructor;
import org.h2.test.db.TestConnectionInfo; import org.h2.test.db.TestConnectionInfo;
import org.h2.tools.DeleteDbFiles; import org.h2.tools.DeleteDbFiles;
...@@ -227,6 +231,11 @@ java org.h2.test.TestAll timer ...@@ -227,6 +231,11 @@ java org.h2.test.TestAll timer
*/ */
public boolean diskResult; 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 * If the transaction log should be kept small (that is, the log should be
* switched early). * switched early).
...@@ -291,14 +300,23 @@ java org.h2.test.TestAll timer ...@@ -291,14 +300,23 @@ java org.h2.test.TestAll timer
System.setProperty("h2.check2", "true"); System.setProperty("h2.check2", "true");
int testing; int testingRecovery;
// System.setProperty("h2.lobInDatabase", "true"); System.setProperty("h2.delayWrongPasswordMin", "0");
// System.setProperty("h2.analyzeAuto", "100"); 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 test with small freeList pages, page size 64
power failure test power failure test
...@@ -306,6 +324,9 @@ power failure test: MULTI_THREADED=TRUE ...@@ -306,6 +324,9 @@ power failure test: MULTI_THREADED=TRUE
power failure test: larger binaries and additional index. power failure test: larger binaries and additional index.
power failure test with randomly generating / dropping indexes and tables. 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; drop table test;
create table test(id identity, name varchar(100) default space(100)); create table test(id identity, name varchar(100) default space(100));
@LOOP 10 insert into test select null, null from system_range(1, 100000); @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` ...@@ -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. * Run the tests with a number of different settings.
*/ */
private void runTests() throws SQLException { 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; jdk14 = true;
smallLog = big = networked = memory = ssl = false; smallLog = big = networked = memory = ssl = false;
diskResult = traceSystemOut = diskUndo = false; diskResult = traceSystemOut = diskUndo = false;
...@@ -557,7 +588,6 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -557,7 +588,6 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestCrashAPI().runTest(this); new TestCrashAPI().runTest(this);
new TestFuzzOptimizations().runTest(this); new TestFuzzOptimizations().runTest(this);
new TestRandomSQL().runTest(this); new TestRandomSQL().runTest(this);
new TestKillRestart().runTest(this); new TestKillRestart().runTest(this);
new TestKillRestartMulti().runTest(this); new TestKillRestartMulti().runTest(this);
new TestMultiThreaded().runTest(this); new TestMultiThreaded().runTest(this);
...@@ -587,6 +617,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -587,6 +617,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestMultiThreadedKernel().runTest(this); new TestMultiThreadedKernel().runTest(this);
new TestOverflow().runTest(this); new TestOverflow().runTest(this);
new TestPageStore().runTest(this); new TestPageStore().runTest(this);
new TestPageStoreCoverage().runTest(this);
new TestPattern().runTest(this); new TestPattern().runTest(this);
new TestPgServer().runTest(this); new TestPgServer().runTest(this);
new TestReader().runTest(this); new TestReader().runTest(this);
...@@ -626,7 +657,8 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -626,7 +657,8 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
*/ */
void beforeTest() throws SQLException { void beforeTest() throws SQLException {
Driver.load(); 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); FileSystemDisk.getInstance().deleteRecursive("trace.db", false);
if (networked) { if (networked) {
String[] args = ssl ? new String[] { "-tcpSSL", "true", "-tcpPort", "9192" } : new String[] { "-tcpPort", 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` ...@@ -646,7 +678,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
if (networked && server != null) { if (networked && server != null) {
server.stop(); server.stop();
} }
DeleteDbFiles.execute(TestBase.baseDir, null, true); FileSystemDisk.getInstance().deleteRecursive(TestBase.BASE_TEST_DIR, false);
} }
private void printSystem() { private void printSystem() {
......
...@@ -27,6 +27,7 @@ import org.h2.jdbc.JdbcConnection; ...@@ -27,6 +27,7 @@ import org.h2.jdbc.JdbcConnection;
import org.h2.message.TraceSystem; import org.h2.message.TraceSystem;
import org.h2.store.FileLock; import org.h2.store.FileLock;
import org.h2.store.fs.FileSystem; import org.h2.store.fs.FileSystem;
import org.h2.test.utils.RecordingFileSystem;
import org.h2.tools.DeleteDbFiles; import org.h2.tools.DeleteDbFiles;
/** /**
...@@ -35,9 +36,9 @@ import org.h2.tools.DeleteDbFiles; ...@@ -35,9 +36,9 @@ import org.h2.tools.DeleteDbFiles;
public abstract class TestBase { 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. * The temporary directory.
...@@ -49,7 +50,10 @@ public abstract class TestBase { ...@@ -49,7 +50,10 @@ public abstract class TestBase {
*/ */
protected static int uniqueId; 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. * The last time something was printed.
...@@ -192,6 +196,27 @@ public abstract class TestBase { ...@@ -192,6 +196,27 @@ public abstract class TestBase {
return getPassword("123"); 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 * Get the database URL for the given database name using the current
* configuration options. * configuration options.
...@@ -208,8 +233,10 @@ public abstract class TestBase { ...@@ -208,8 +233,10 @@ public abstract class TestBase {
if (config.memory) { if (config.memory) {
name = "mem:" + name; name = "mem:" + name;
} else { } else {
if (!name.startsWith("memFS:") && !name.startsWith(baseDir + "/")) { int idx = name.indexOf(':');
name = baseDir + "/" + name; if (idx < 0 || idx > 10) {
// index > 10 if in options
name = getBaseDir() + "/" + name;
} }
} }
if (config.networked) { if (config.networked) {
...@@ -458,7 +485,7 @@ public abstract class TestBase { ...@@ -458,7 +485,7 @@ public abstract class TestBase {
* @param name the database name * @param name the database name
*/ */
protected void deleteDb(String name) throws SQLException { protected void deleteDb(String name) throws SQLException {
deleteDb(baseDir, name); deleteDb(getBaseDir(), name);
} }
/** /**
......
...@@ -12,13 +12,13 @@ import org.h2.store.fs.FileObject; ...@@ -12,13 +12,13 @@ import org.h2.store.fs.FileObject;
/** /**
* A debugging file that logs all operations. * 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 FileObject file;
private final String name; private final String name;
FileObjectDebug(FileSystemDebug fs, FileObject file) { DebugFileObject(DebugFileSystem fs, FileObject file) {
this.fs = fs; this.fs = fs;
this.file = file; this.file = file;
this.name = file.getName(); this.name = file.getName();
...@@ -36,7 +36,7 @@ public class FileObjectDebug implements FileObject { ...@@ -36,7 +36,7 @@ public class FileObjectDebug implements FileObject {
public String getName() { public String getName() {
debug("getName"); debug("getName");
return FileSystemDebug.PREFIX + file.getName(); return DebugFileSystem.PREFIX + file.getName();
} }
public long length() throws IOException { public long length() throws IOException {
......
...@@ -16,14 +16,14 @@ import org.h2.store.fs.FileSystem; ...@@ -16,14 +16,14 @@ import org.h2.store.fs.FileSystem;
/** /**
* A debugging file system that logs all operations. * 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. * The prefix used for a debugging file system.
*/ */
static final String PREFIX = "debug:"; 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"); private static final IOException POWER_OFF = new IOException("Simulated power failure");
...@@ -155,6 +155,12 @@ public class FileSystemDebug extends FileSystem { ...@@ -155,6 +155,12 @@ public class FileSystemDebug extends FileSystem {
return FileSystem.getInstance(fileName).isReadOnly(fileName); 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) { public long length(String fileName) {
fileName = translateFileName(fileName); fileName = translateFileName(fileName);
trace("length", fileName); trace("length", fileName);
...@@ -186,7 +192,7 @@ public class FileSystemDebug extends FileSystem { ...@@ -186,7 +192,7 @@ public class FileSystemDebug extends FileSystem {
public FileObject openFileObject(String fileName, String mode) throws IOException { public FileObject openFileObject(String fileName, String mode) throws IOException {
fileName = translateFileName(fileName); fileName = translateFileName(fileName);
trace("openFileObject", fileName, mode); 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) { public OutputStream openFileOutputStream(String fileName, boolean append) {
...@@ -199,7 +205,7 @@ public class FileSystemDebug extends FileSystem { ...@@ -199,7 +205,7 @@ public class FileSystemDebug extends FileSystem {
oldName = translateFileName(oldName); oldName = translateFileName(oldName);
newName = translateFileName(newName); newName = translateFileName(newName);
trace("rename", oldName, newName); trace("rename", oldName, newName);
FileSystem.getInstance(oldName).copy(oldName, newName); FileSystem.getInstance(oldName).rename(oldName, newName);
} }
public boolean tryDelete(String fileName) { 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论