mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-23 12:49:45 +00:00
GP-2181 - removed JMockit usage from some tests (part 3)
This commit is contained in:
parent
91e5259018
commit
0a32484b5b
@ -15,27 +15,25 @@
|
||||
*/
|
||||
package ghidra.framework.data;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.framework.model.DomainObjectLockedException;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import mockit.*;
|
||||
|
||||
public class TransactionLockingTest extends AbstractGenericTest {
|
||||
|
||||
private Program program;
|
||||
|
||||
private CountDownLatch programLockedLatch = new CountDownLatch(1);
|
||||
private CountDownLatch lockExceptionLatch = new CountDownLatch(1);
|
||||
// placeholder for exceptions encountered while testing; this will be checked at the end
|
||||
private Exception unexpectedException = null;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@ -43,80 +41,89 @@ public class TransactionLockingTest extends AbstractGenericTest {
|
||||
program = builder.getProgram();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
program.release(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test attempts to verify 2 things: 1) that a client attempting to start a transaction
|
||||
* on a locked domain object will get an exception, and 2) the client will keep waiting after
|
||||
* getting the exception, due to the use of a while(true) loop in the system under test.
|
||||
*/
|
||||
@Test
|
||||
public void testTransactionWaitForLock() throws Exception {
|
||||
public void testTransactionWaitForLock() {
|
||||
|
||||
// Inject DomainObjectLockedException mock to trigger lockExceptionLatch
|
||||
new SpyDomainObjectLockedException();
|
||||
//
|
||||
// The latch uses a count of 3, which is an arbitrary number greater than 1. This allows
|
||||
// us to know that the system performed the expected spin/wait loop more than once.
|
||||
//
|
||||
CountDownLatch clientWaitLatch = new CountDownLatch(3);
|
||||
Function<DomainObjectLockedException, Boolean> exceptionHandler = e -> {
|
||||
clientWaitLatch.countDown();
|
||||
return true; // keep waiting; the client will get released by the lock thread
|
||||
};
|
||||
|
||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
||||
|
||||
// setup transaction thread
|
||||
//
|
||||
// Thread that will block while waiting to start the transaction
|
||||
//
|
||||
CountDownLatch lockThreadLatch = new CountDownLatch(1);
|
||||
Thread txThread = new Thread(() -> {
|
||||
try {
|
||||
programLockedLatch.await(2, TimeUnit.SECONDS);
|
||||
int txId = program.startTransaction("Test"); // Block until lock released
|
||||
lockThreadLatch.await(2, TimeUnit.SECONDS);
|
||||
|
||||
//
|
||||
// Cast to db to use package-level method for testing.
|
||||
//
|
||||
// The call to startTransaction() will block until our exception handler signals
|
||||
// to continue.
|
||||
//
|
||||
DomainObjectAdapterDB programDb = (DomainObjectAdapterDB) program;
|
||||
int txId = programDb.startTransaction("Test", null, exceptionHandler);
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
catch (Exception e) {
|
||||
exceptionRef.set(e);
|
||||
unexpectedException = e;
|
||||
}
|
||||
|
||||
}, "Tx-Thread");
|
||||
}, "Tx-Client-Thread");
|
||||
txThread.start();
|
||||
|
||||
//setup lock thread
|
||||
//
|
||||
// Thread that will acquire the program lock, which will block the other thread
|
||||
//
|
||||
Thread lockThread = new Thread(() -> {
|
||||
boolean gotLock = program.lock("TestLock");
|
||||
if (!gotLock) {
|
||||
exceptionRef.set(new AssertException("Failed to obtain lock"));
|
||||
if (!program.lock("TestLock")) {
|
||||
unexpectedException = new AssertException("Failed to obtain program lock");
|
||||
lockThreadLatch.countDown(); // signal for the Transaction Thread to proceed
|
||||
return;
|
||||
}
|
||||
|
||||
programLockedLatch.countDown(); // signal for startTransaction
|
||||
|
||||
if (gotLock) {
|
||||
try {
|
||||
lockExceptionLatch.await(2, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// unexpected
|
||||
exceptionRef.set(e);
|
||||
}
|
||||
finally {
|
||||
program.unlock();
|
||||
}
|
||||
lockThreadLatch.countDown(); // signal for the Transaction Thread to proceed
|
||||
try {
|
||||
// keep blocking until we know the client has waited for our test exception handler
|
||||
// to signal to continue
|
||||
clientWaitLatch.await(2, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
unexpectedException = new AssertException(
|
||||
"Test thread interrupted waiting for client transaction thread", e);
|
||||
}
|
||||
finally {
|
||||
program.unlock();
|
||||
}
|
||||
|
||||
}, "Lock-Thread");
|
||||
}, "Program-Lock-Thread");
|
||||
lockThread.start();
|
||||
|
||||
// wait for transaction test thread to complete
|
||||
txThread.join(2000);
|
||||
assertFalse("Tx-Thread may be hung", txThread.isAlive());
|
||||
try {
|
||||
txThread.join(2000);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
unexpectedException = new AssertException(
|
||||
"Test thread interrupted waiting for client transaction thread", e);
|
||||
}
|
||||
|
||||
Exception exc = exceptionRef.get();
|
||||
if (exc != null) {
|
||||
failWithException("Transaction Failure", exc);
|
||||
if (unexpectedException != null) {
|
||||
failWithException("Unexpected test failure", unexpectedException);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class SpyDomainObjectLockedException extends MockUp<DomainObjectLockedException> {
|
||||
|
||||
/**
|
||||
* Mock/Inject constructor for DomainObjectLockedException to provide detection
|
||||
* of its construction via lockExceptionLatch
|
||||
* @param invocation
|
||||
* @param reason
|
||||
*/
|
||||
@Mock
|
||||
public void $init(Invocation invocation, String reason) {
|
||||
lockExceptionLatch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package ghidra.framework.data;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import db.DBConstants;
|
||||
import db.DBHandle;
|
||||
@ -38,12 +39,12 @@ import ghidra.util.task.TaskMonitorAdapter;
|
||||
* concept of starting a transaction before a change is made to the
|
||||
* domain object and ending the transaction. The transaction allows for
|
||||
* undo/redo changes.
|
||||
*
|
||||
*
|
||||
* The implementation class must also satisfy the following requirements:
|
||||
* <pre>
|
||||
*
|
||||
*
|
||||
* 1. The following constructor signature must be implemented:
|
||||
*
|
||||
*
|
||||
* **
|
||||
* * Constructs new Domain Object
|
||||
* * @param dbh a handle to an open domain object database.
|
||||
@ -53,16 +54,16 @@ import ghidra.util.task.TaskMonitorAdapter;
|
||||
* * UPGRADE: the database is upgraded to the latest schema as it is opened.
|
||||
* * @param monitor TaskMonitor that allows the open to be cancelled.
|
||||
* * @param consumer the object that keeping the program open.
|
||||
* *
|
||||
* *
|
||||
* * @throws IOException if an error accessing the database occurs.
|
||||
* * @throws VersionException if database version does not match implementation. UPGRADE may be possible.
|
||||
* **
|
||||
* public DomainObjectAdapterDB(DBHandle dbh, int openMode, TaskMonitor monitor, Object consumer) throws IOException, VersionException
|
||||
* public DomainObjectAdapterDB(DBHandle dbh, int openMode, TaskMonitor monitor, Object consumer) throws IOException, VersionException
|
||||
*
|
||||
* 2. The following static field must be provided:
|
||||
*
|
||||
*
|
||||
* public static final String CONTENT_TYPE
|
||||
*
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
@ -82,6 +83,23 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
|
||||
private AbstractTransactionManager transactionMgr;
|
||||
|
||||
//
|
||||
// This exception handler will sleep, allowing the startTransaction() method to ignore
|
||||
// the exception and then try again, since it is using a while(true) loop. This can
|
||||
// happen if clients try to start a transaction during a long running operation, such as
|
||||
// a program save.
|
||||
//
|
||||
Function<DomainObjectLockedException, Boolean> tryForeverExceptionHandler = e -> {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
Msg.debug(this, "Thread interrupted while waiting for program lock: '" +
|
||||
Thread.currentThread().getName() + "'", ie);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a new DomainObjectAdapterDB object.
|
||||
* If construction of this object fails, be sure to release with consumer
|
||||
@ -109,7 +127,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
|
||||
/**
|
||||
* Flush any pending database changes.
|
||||
* This method will be invoked by the transaction manager
|
||||
* This method will be invoked by the transaction manager
|
||||
* prior to closing a transaction.
|
||||
*/
|
||||
public void flushWriteCache() {
|
||||
@ -117,14 +135,14 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
|
||||
/**
|
||||
* Invalidate (i.e., clear) any pending database changes not yet written.
|
||||
* This method will be invoked by the transaction manager
|
||||
* This method will be invoked by the transaction manager
|
||||
* prior to aborting a transaction.
|
||||
*/
|
||||
public void invalidateWriteCache() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of all domain objects synchronized with a
|
||||
* Return array of all domain objects synchronized with a
|
||||
* shared transaction manager.
|
||||
* @return returns array of synchronized domain objects or
|
||||
* null if this domain object is not synchronized with others.
|
||||
@ -139,9 +157,9 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
|
||||
/**
|
||||
* Synchronize the specified domain object with this domain object
|
||||
* using a shared transaction manager. If either or both is already shared,
|
||||
* a transition to a single shared transaction manager will be
|
||||
* performed.
|
||||
* using a shared transaction manager. If either or both is already shared,
|
||||
* a transition to a single shared transaction manager will be
|
||||
* performed.
|
||||
* @param domainObj
|
||||
* @throws LockException if lock or open transaction is active on either
|
||||
* this or the specified domain object
|
||||
@ -222,7 +240,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
|
||||
/**
|
||||
* Returns all properties lists contained by this domain object.
|
||||
*
|
||||
*
|
||||
* @return all property lists contained by this domain object.
|
||||
*/
|
||||
@Override
|
||||
@ -241,7 +259,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to perform property list alterations resulting from renamed or obsolete
|
||||
* This method can be used to perform property list alterations resulting from renamed or obsolete
|
||||
* property paths. This should only be invoked during an upgrade.
|
||||
* WARNING! Should only be called during construction of domain object
|
||||
* @see OptionsDB#performAlterations(Map)
|
||||
@ -317,22 +335,23 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
|
||||
@Override
|
||||
public int startTransaction(String description, AbortedTransactionListener listener) {
|
||||
int id = -1;
|
||||
while (id == -1) {
|
||||
return startTransaction(description, listener, tryForeverExceptionHandler);
|
||||
}
|
||||
|
||||
// open for testing
|
||||
int startTransaction(String description, AbortedTransactionListener listener,
|
||||
Function<DomainObjectLockedException, Boolean> exceptionHandler) {
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
id = transactionMgr.startTransaction(this, description, listener, true);
|
||||
return transactionMgr.startTransaction(this, description, listener, true);
|
||||
}
|
||||
catch (DomainObjectLockedException e) {
|
||||
// wait for lock to be removed (e.g., Save operation)
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e1) {
|
||||
Msg.debug(this, "Unexpected thread interrupt", e1);
|
||||
if (!exceptionHandler.apply(e)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -510,7 +529,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
|
||||
// TODO :( output method will cause Redo-able transactions to be cleared
|
||||
// and may cause older Undo-able transactions to be cleared.
|
||||
// Should implement transaction listener to properly maintain domain object
|
||||
// Should implement transaction listener to properly maintain domain object
|
||||
// transaction sychronization
|
||||
|
||||
}
|
||||
@ -520,9 +539,9 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called before a save, saveAs, or saveToPackedFile
|
||||
* This method is called before a save, saveAs, or saveToPackedFile
|
||||
* to update common meta data
|
||||
* @throws IOException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void updateMetadata() throws IOException {
|
||||
saveMetadata();
|
||||
|
Loading…
Reference in New Issue
Block a user