//
// Copyright (C) 2010 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.test.vm.threads;
import org.junit.Test;
import gov.nasa.jpf.util.test.TestJPF;
import gov.nasa.jpf.vm.Verify;
/**
* regression test for various Thread.join scenarios
*/
public class JoinTest extends TestJPF {
static final String[] JPF_ARGS = { "+cg.threads.break_start=true",
"+cg.threads.break_yield=true",
"+vm.tree_output=false",
"+vm.path_output=true"};
@Test public void testSimpleJoin(){
if (verifyNoPropertyViolation(JPF_ARGS)) {
Runnable r = new Runnable() {
public void run() {
System.out.println("thread-0 run");
}
};
Thread t = new Thread(r);
t.start();
try {
t.join();
assert !t.isAlive();
System.out.println("main returned from join");
} catch (InterruptedException x) {
fail("join() did throw InterruptedException");
}
}
}
@Test public void testNoRunnableSimpleJoin() {
if (verifyNoPropertyViolation(JPF_ARGS)) {
Thread t = new Thread() {
public synchronized void run() {
System.out.println("thread-0 run");
}
};
t.start();
try {
t.join();
assert !t.isAlive();
System.out.println("main returned from join");
} catch (InterruptedException x) {
fail("join() did throw InterruptedException");
}
}
}
static class SomeThread extends Thread {
Object o;
public void run() {
synchronized (this){
// this causes a transition break - write on a shared object while
// we still hold the lock
o = new Object();
}
System.out.println("thread-0 done");
}
}
@Test public void testBlockedJoin() {
if (verifyNoPropertyViolation("+cg.threads.break_start=true",
"+vm.storage.class=null")) {
Thread t = new SomeThread();
t.start();
System.out.println("main started thread-0");
try {
t.join();
assert !t.isAlive();
System.out.println("main returned from join");
} catch (InterruptedException x) {
fail("join() did throw InterruptedException");
}
}
}
@Test public void testJoinHoldingLock(){
if (verifyNoPropertyViolation(JPF_ARGS)) {
Runnable r = new Runnable() {
public void run() {
System.out.println("thread-0 run");
}
};
Thread t = new Thread(r);
t.start();
try {
synchronized(t){
t.join();
}
System.out.println("main returned from join");
} catch (InterruptedException x) {
fail("join() did throw InterruptedException");
}
}
}
@Test public void testNotAliveJoin(){
if (verifyNoPropertyViolation(JPF_ARGS)) {
Runnable r = new Runnable() {
public void run() {
System.out.println("thread-0 run");
}
};
Thread t = new Thread(r);
t.start();
// poor man's join
while (t.isAlive()){
Thread.yield();
}
try {
t.join();
System.out.println("main returned from join");
} catch (InterruptedException x) {
fail("join() did throw InterruptedException");
}
}
}
@Test public void testPreJoinInterrupt() {
if (verifyNoPropertyViolation(JPF_ARGS)) {
Runnable r = new Runnable() {
public void run() {
System.out.println("thread-0 run");
}
};
Thread.currentThread().interrupt();
Thread t = new Thread(r);
t.start();
try {
t.join();
fail("join() didn't throw InterruptedException");
} catch (InterruptedException x) {
System.out.println("caught InterruptedException");
}
}
}
@Test public void testInterruptedJoin() {
if (verifyNoPropertyViolation(JPF_ARGS)) {
final Thread mainThread = Thread.currentThread();
Runnable r = new Runnable() {
public void run() {
System.out.println("thread-0 interrupting main");
mainThread.interrupt();
}
};
Thread t = new Thread(r);
t.start();
try {
t.join();
fail("join() didn't throw InterruptedException");
} catch (InterruptedException x) {
System.out.println("caught InterruptedException");
}
}
}
@Test public void testJoinLoop() {
if (verifyDeadlock(JPF_ARGS)) {
try {
Thread.currentThread().join();
fail("main can't get here if waiting for itself");
} catch (InterruptedException ex) {
fail("thread cannot be interrupted");
}
}
}
@Test public void testMultipleJoins() {
if (verifyNoPropertyViolation(JPF_ARGS)) {
try {
final Thread t1 = new Thread() {
public void run() {
Thread.yield();
}
};
Thread t2 = new Thread() {
public void run() {
try {
t1.join();
} catch (InterruptedException e) {
fail("unexpected interrupt");
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
assert (!t1.isAlive() && !t2.isAlive());
} catch (Exception ex) {
fail("unexpected exception: " + ex);
}
}
}
@Test public void testJoinBeforeStart() {
if (verifyNoPropertyViolation(JPF_ARGS)) {
try {
Thread t = new Thread();
t.join();
System.out.println("join on not-yet-started thread has no effect");
} catch (Exception ex) {
fail(ex.getMessage());
}
}
}
@Test public void testJoinAfterNotify() {
if (verifyNoPropertyViolation(JPF_ARGS)) {
try {
final Thread t = new Thread() {
public void run() {
synchronized(this){
System.out.println("thread-0 notifying");
notifyAll(); // this should not get us out of the join()
}
System.out.println("thread-0 terminating");
}
};
t.start();
System.out.println("main joining..");
t.join();
System.out.println("main joined");
Verify.printPathOutput("main termination");
assert !t.isAlive();
} catch (Exception ex) {
fail("unexpected exception: " + ex);
}
}
}
@Test public void testJoinNotifyDeadlock() {
if (verifyDeadlock(JPF_ARGS)) {
try {
final Thread t = new Thread() {
public void run() {
synchronized(this){
System.out.println("thread-0 notifying");
notifyAll();
try {
System.out.println("thread-0 waiting");
wait();
} catch (InterruptedException ix){
System.out.println("unexpected interrupt");
}
}
System.out.println("thread-0 terminating");
}
};
t.start();
System.out.println("main joining..");
t.join();
System.out.println("main joined");
synchronized (t){
System.out.println("main notifying");
t.notify();
}
Verify.printPathOutput("main termination");
assert !t.isAlive();
} catch (Exception ex) {
fail("unexpected exception: " + ex);
}
}
}
@Test public void testRedundantJoin() {
if (verifyNoPropertyViolation(JPF_ARGS)) {
try {
Thread t = new Thread();
t.start();
t.join();
System.out.println("main returned from first join()");
t.join();
System.out.println("main returned from second join()");
assert (!t.isAlive());
} catch (Exception ex) {
fail(ex.getMessage());
}
}
}
@Test public void testJoinThreadSet(){
if (verifyNoPropertyViolation(JPF_ARGS)) {
final Thread[] worker = new Thread[3];
worker[0] = new Thread(new Runnable() {
public void run() {
System.out.println("worker[0] finished");
}
});
worker[1] = new Thread(new Runnable() {
public void run() {
System.out.println("worker[1] finished");
}
});
worker[2] = new Thread(new Runnable() {
public void run() {
System.out.println("worker[2] finished");
}
});
int nJoin = 0;
for (int i = 0; i < worker.length; i++) {
worker[i].start();
nJoin++;
}
while (nJoin > 0) {
for (int i = 0; i < worker.length; i++) {
if (worker[i] != null) {
try {
worker[i].join();
} catch (InterruptedException x) {
fail("unexpected interrupt");
}
nJoin--;
worker[i] = null;
System.out.println("main joined worker[" + i + "]");
}
}
}
}
}
@Test public void testRecursiveJoinThreadGroup() {
if (!isJPFRun()){
Verify.resetCounter(0);
Verify.resetCounter(1);
Verify.resetCounter(2);
Verify.resetCounter(3);
}
if (verifyNoPropertyViolation(JPF_ARGS)) {
ThreadGroup workers = new ThreadGroup("workers");
Thread t = new Thread( workers, new Runnable(){
public void run() {
Thread t1 = new Thread( new Runnable(){
public void run() {
Thread t11 = new Thread(new Runnable() {
public void run() {
System.out.println("t11 run");
Verify.incrementCounter(0);
}
}, "t11");
t11.start();
System.out.println("t1 run");
Verify.incrementCounter(1);
}
}, "t1");
t1.start();
Thread t2 = new Thread( new Runnable(){
public void run() {
System.out.println("t2 run");
Verify.incrementCounter(2);
}
}, "t2");
t2.start();
System.out.println("t run");
Verify.incrementCounter(3);
}
}, "t");
t.start();
try {
Thread[] actives = new Thread[10]; // the length is just a guess here
int nActives = workers.enumerate(actives, true);
System.out.println("main joining " + nActives + " active threads");
while (nActives > 0){
assert nActives < actives.length; // it has to be strictly less to know we've got all
for (int i=0; i<nActives; i++){
System.out.println("main joining: " + actives[i].getName());
actives[i].join();
System.out.println("main joined: " + actives[i].getName());
}
nActives = workers.enumerate(actives, true);
System.out.println("..main now joining " + nActives + " active threads");
}
} catch (Throwable x){
fail("unexpected exception: " + x);
}
System.out.println("main done");
Verify.printPathOutput("end");
}
if (!isJPFRun()){
// not an ideal test since we don't know if the threads are still alive
assert Verify.getCounter(0) > 0;
assert Verify.getCounter(1) > 0;
assert Verify.getCounter(2) > 0;
assert Verify.getCounter(3) > 0;
}
}
@Test
public void testInterruptThreadWaitingToJoin() {
if (!isJPFRun()){
Verify.resetCounter(0);
}
if (verifyNoPropertyViolation(JPF_ARGS)) {
try {
class ChildThread extends Thread {
Thread toInterrupt;
public void setToInterrupt(Thread toInterrupt) {
this.toInterrupt = toInterrupt;
}
public void run() {
toInterrupt.interrupt();
}
}
final ChildThread child = new ChildThread();
class WaitingToJoinThread extends Thread {
public void run() {
try {
child.setToInterrupt(this);
child.start();
child.join();
} catch (InterruptedException ix) {
System.out.println("-- parent interrupted while child continues to run");
Verify.incrementCounter(0);
}
}
}
WaitingToJoinThread threadWaitingToJoin = new WaitingToJoinThread();
threadWaitingToJoin.start();
try {
threadWaitingToJoin.join();
} catch (InterruptedException ix) {
throw new RuntimeException("main thread was interrupted");
}
try {
child.join();
} catch (InterruptedException ix) {
throw new RuntimeException("main thread was interrupted");
}
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage());
}
}
if (!isJPFRun()){
// at least one execution interrupts parent while child continues to run
assert Verify.getCounter(0) > 0;
}
}
@Test public void testTimeoutJoin () {
if (!isJPFRun()){
Verify.resetCounter(0);
Verify.resetCounter(1);
}
if (verifyNoPropertyViolation(JPF_ARGS)) {
Runnable r = new Runnable() {
public void run() {
System.out.println("thread-0 run");
Thread.yield();
}
};
Thread t = new Thread(r);
t.start();
//Thread.yield();
try {
System.out.println("main joining..");
t.join(42);
System.out.println("main joined, t state: " + t.getState());
// we should get here for both terminated and non-terminated thread
switch (t.getState()) {
case TERMINATED:
System.out.println("got terminated case");
Verify.incrementCounter(0);
break;
case RUNNABLE:
System.out.println("got timedout case");
Verify.incrementCounter(1);
break;
default:
fail("infeasible thread state: " + t.getState());
}
} catch (InterruptedException ix) {
fail("main thread was interrupted");
}
}
if (!isJPFRun()) {
assert Verify.getCounter(0) > 0;
assert Verify.getCounter(1) > 0;
}
}
@Test public void testZeroTimeoutJoin () {
if (!isJPFRun()){
Verify.resetCounter(0);
Verify.resetCounter(1);
}
if (verifyNoPropertyViolation(JPF_ARGS)) {
Runnable r = new Runnable() {
public void run() {
System.out.println("thread-0 run");
Thread.yield();
}
};
Thread t = new Thread(r);
t.start();
//Thread.yield();
try {
System.out.println("main joining..");
t.join(0);
System.out.println("main joined, t state: " + t.getState());
// we should get here for both terminated and non-terminated thread
switch (t.getState()) {
case TERMINATED:
Verify.incrementCounter(0);
break;
case RUNNABLE:
Verify.incrementCounter(1);
break;
default:
fail("infeasible thread state: " + t.getState());
}
} catch (InterruptedException ix) {
fail("main thread was interrupted");
}
}
if (!isJPFRun()) {
assert Verify.getCounter(0) > 0;
assert Verify.getCounter(1) == 0;
}
}
@Test public void testNegativeTimeoutJoin() {
if (verifyNoPropertyViolation(JPF_ARGS)) {
try {
Thread t = new Thread();
t.join(-1);
fail("should never get here");
} catch (InterruptedException ix) {
fail("unexpected InterruptedException");
} catch (IllegalArgumentException ax){
System.out.println("caught " + ax);
}
}
}
@Test public void testNestedLocksJoin() {
if (verifyNoPropertyViolation(JPF_ARGS)){
Thread t1 = new Thread() {
public synchronized void run() {
System.out.println("t1 notifying");
notifyAll();
try{
System.out.println("t1 waiting");
wait();
} catch (InterruptedException ix){
System.out.println("t1 unexpectedly interrupted");
}
System.out.println("t1 terminating");
}
};
Thread t2 = new Thread() {
public synchronized void run() {
System.out.println("t2 notifying");
notifyAll();
try{
System.out.println("t2 waiting");
wait();
} catch (InterruptedException ix){
System.out.println("t2 unexpectedly interrupted");
}
System.out.println("t2 terminating");
}
};
synchronized (t2){
try {
t2.start();
System.out.println("main waiting on t2");
t2.wait();
synchronized(t1){
t1.start();
System.out.println("main waiting on t1");
t1.wait();
System.out.println("main notifying t1");
t1.notify();
}
System.out.println("main joining t1");
t1.join();
System.out.println("main notifying t2");
t2.notify();
System.out.println("main joining t2");
t2.join();
} catch (InterruptedException ix){
System.out.println("main unexpectedly interrupted");
}
}
System.out.println("main terminating");
Verify.printPathOutput("main termination");
}
}
}