package au.pfip; import com.sun.net.httpserver.HttpServer; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.concurrent.TimeUnit; public class Harness2 { static int failures = 0; static void check(String name, boolean ok) { System.out.println((ok ? "PASS " : "FAIL ") + name); if (!ok) failures++; } public static void main(String[] a) throws Exception { Method checkUser = Main.class.getDeclaredMethod("checkUser", String.class); checkUser.setAccessible(true); HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); final String[] body = {"0"}; final int[] status = {200}; final java.util.List queries = new java.util.concurrent.CopyOnWriteArrayList<>(); server.createContext("/", ex -> { queries.add(ex.getRequestURI().toString()); byte[] b = body[0].getBytes(StandardCharsets.UTF_8); ex.sendResponseHeaders(status[0], b.length); ex.getResponseBody().write(b); ex.close(); }); server.start(); Main.PlaytimeURL = "http://127.0.0.1:" + server.getAddress().getPort() + "/timer.php"; String me = System.getProperty("user.name"); // the student who is playing String other = "nobodyxyz"; // a monitored student who is not playing int n = 40000 + new Random().nextInt(9000); String game = "sleep " + n; Main.GamesList = new String[]{game}; // 1. The old bug: the playing student is checked FIRST, a non-playing student LAST. Process p1 = new ProcessBuilder("sleep", String.valueOf(n)).start(); Thread.sleep(300); body[0] = "0"; checkUser.invoke(null, me); checkUser.invoke(null, other); check("0 minutes: playing student's game is closed even though the next student isn't playing", p1.waitFor(15, TimeUnit.SECONDS)); check("server was asked about the playing student only once", queries.size() == 1 && queries.get(0).contains("user=" + me) && queries.get(0).contains("game=sleep+" + n)); // 2. Minutes left: nothing is killed. Process p2 = new ProcessBuilder("sleep", String.valueOf(n)).start(); Thread.sleep(300); body[0] = "10"; checkUser.invoke(null, me); check("10 minutes left: game keeps running", p2.isAlive()); // 3. Server problems: closed only after OfflineChecksBeforeKill failures in a row. settings.addSetting("OfflineChecksBeforeKill", "2"); status[0] = 500; checkUser.invoke(null, me); check("1st failed check: game keeps running", p2.isAlive()); status[0] = 200; body[0] = "10"; checkUser.invoke(null, me); // a good answer resets the count status[0] = 500; checkUser.invoke(null, me); check("failure count was reset by a good answer: still running", p2.isAlive()); checkUser.invoke(null, me); check("2 failed checks in a row: game is closed", p2.waitFor(15, TimeUnit.SECONDS)); // 4. A text reason from the server closes the game. Process p3 = new ProcessBuilder("sleep", String.valueOf(n)).start(); Thread.sleep(300); status[0] = 200; body[0] = "You are not within allowed play times."; checkUser.invoke(null, me); check("text reason: game is closed", p3.waitFor(15, TimeUnit.SECONDS)); // 5. No game running: server isn't called. queries.clear(); checkUser.invoke(null, me); check("no game running: server not called", queries.isEmpty()); server.stop(0); System.out.println(failures == 0 ? "ALL PASSED" : failures + " FAILED"); System.exit(failures == 0 ? 0 : 1); } }