Harness.java 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package au.pfip;
  2. import com.sun.net.httpserver.HttpServer;
  3. import java.net.InetSocketAddress;
  4. import java.nio.charset.StandardCharsets;
  5. import java.util.*;
  6. public class Harness {
  7. static int failures = 0;
  8. static void check(String name, boolean ok) {
  9. System.out.println((ok ? "PASS " : "FAIL ") + name);
  10. if (!ok) failures++;
  11. }
  12. public static void main(String[] a) throws Exception {
  13. String me = System.getProperty("user.name");
  14. // --- user parsing
  15. check("parseUsers trims/dedupes/filters",
  16. Arrays.equals(Main.parseUsers(" alice, bob ,alice,,bad name;rm,$(x)"), new String[]{"alice","bob"}));
  17. check("parseUsers empty", Main.parseUsers("").length == 0);
  18. // --- games json parsing
  19. List<String> g1 = communications.parseUniqueValues("[\"minecraft\",\"steam\",\"minecraft\",\"bad;name\"]");
  20. check("games array deduped + bad name dropped", g1.equals(List.of("minecraft","steam")));
  21. List<String> g2 = communications.parseUniqueValues("{\"a\":\"roblox\",\"b\":\"fortnite\"}");
  22. check("games object values", g2.equals(List.of("roblox","fortnite")));
  23. // --- Shell
  24. List<String> echo = Shell.run(5, "echo", "hi $(whoami) \"q\"");
  25. check("Shell.run passes text literally (no shell)", echo != null && echo.get(0).equals("hi $(whoami) \"q\""));
  26. long t0 = System.currentTimeMillis();
  27. check("Shell.run times out", Shell.run(1, "sleep", "30") == null && System.currentTimeMillis() - t0 < 5000);
  28. check("Shell.run non-zero exit -> null", Shell.run(5, "false") == null);
  29. // --- Processes
  30. int n = 40000 + new Random().nextInt(9000);
  31. Process p1 = new ProcessBuilder("sleep", String.valueOf(n)).start();
  32. Process p2 = new ProcessBuilder("sleep", String.valueOf(n + 1)).start();
  33. Thread.sleep(500);
  34. List<Processes.Proc> snap = Processes.snapshot(me);
  35. check("snapshot finds my process", Processes.matching(snap, "sleep " + n).size() == 1);
  36. check("snapshot excludes other text", Processes.matching(snap, "sleep " + (n + 5000)).isEmpty());
  37. check("terminate returns true", Processes.terminate(me, "sleep " + n));
  38. p1.waitFor();
  39. check("target process is dead", !p1.isAlive());
  40. check("other process untouched", p2.isAlive());
  41. p2.destroyForcibly();
  42. // a process that ignores SIGTERM must be force-killed
  43. Process stubborn = new ProcessBuilder("bash", "-c", "trap '' TERM; exec -a stubborn-game-" + (n + 7) + " sleep 600").start();
  44. Thread.sleep(700);
  45. String stubName = "stubborn-game-" + (n + 7);
  46. check("stubborn process found", Processes.matching(Processes.snapshot(me), stubName).size() == 1);
  47. check("stubborn (ignores SIGTERM) is force-killed", Processes.terminate(me, stubName) && !stubborn.isAlive());
  48. // --- whole-word matching
  49. ProcessHandle dummy = ProcessHandle.current();
  50. java.util.function.BiPredicate<String, String> hit = (cmd, game) ->
  51. !Processes.matching(List.of(new Processes.Proc(dummy, cmd)), game).isEmpty();
  52. check("technic matches ~/.technic path", hit.test("java -jar /home/x/.technic/launcher.jar", "technic"));
  53. check("technic does NOT match technical-report", !hit.test("libreoffice /home/x/technical-report.odt", "technic"));
  54. check("opera matches /usr/lib/opera/opera", hit.test("/usr/lib/opera/opera --type=renderer", "opera"));
  55. check("opera does NOT match operate/cooperative", !hit.test("tool --operate cooperative.txt", "opera"));
  56. check("minecraft matches minecraft-launcher", hit.test("/opt/minecraft-launcher/minecraft-launcher", "minecraft"));
  57. check("minecraft matches -Dminecraft.launcher", hit.test("java -Dminecraft.launcher.brand=x -jar y", "minecraft"));
  58. check("wine exe matches", hit.test("wine Z:\\games\\bf2.exe", "bf2.exe"));
  59. check("underscore names match", hit.test("/x/hl2_linux -game hl2", "hl2_linux"));
  60. check("firefox-esr matches firefox", hit.test("/usr/lib/firefox-esr/firefox-esr", "firefox"));
  61. settings.addSetting("MatchWholeWords", "false");
  62. check("MatchWholeWords=false is plain contains", hit.test("libreoffice /home/x/technical-report.odt", "technic"));
  63. settings.addSetting("MatchWholeWords", "true");
  64. // --- ignore list
  65. Process kiosk = new ProcessBuilder("bash", "-c", "exec -a 'kgame-" + (n + 11) + " --kiosk' sleep 600").start();
  66. Thread.sleep(500);
  67. String kName = "kgame-" + (n + 11);
  68. check("process found without ignore list", Processes.matching(Processes.snapshot(me), kName).size() == 1);
  69. settings.addSetting("IgnoreCommandLines", "--kiosk, -contentproc");
  70. check("process ignored with IgnoreCommandLines=--kiosk", Processes.matching(Processes.snapshot(me), kName).isEmpty());
  71. settings.addSetting("IgnoreCommandLines", "");
  72. kiosk.destroyForcibly();
  73. // --- communications against a local fake server
  74. HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
  75. final String[] body = {"12"}; final int[] status = {200};
  76. server.createContext("/", ex -> {
  77. byte[] b = body[0].getBytes(StandardCharsets.UTF_8);
  78. ex.sendResponseHeaders(status[0], b.length);
  79. ex.getResponseBody().write(b); ex.close();
  80. });
  81. server.start();
  82. int port = server.getAddress().getPort();
  83. Main.PlaytimeURL = "http://127.0.0.1:" + port + "/timer.php";
  84. Main.GamesURL = "http://127.0.0.1:" + port + "/games.json";
  85. body[0] = "12"; check("numeric reply", communications.getPlaytimeFromServer(me, "g") == 12);
  86. body[0] = "0"; check("zero reply", communications.getPlaytimeFromServer(me, "g") == 0);
  87. body[0] = "1440"; check("1440 reply", communications.getPlaytimeFromServer(me, "g") == 1440);
  88. body[0] = ""; check("empty reply -> UNKNOWN", communications.getPlaytimeFromServer(me, "g") == communications.UNKNOWN);
  89. body[0] = "<b>Fatal error</b> in \"x\" `id`"; check("html reply -> UNKNOWN", communications.getPlaytimeFromServer(me, "g") == communications.UNKNOWN);
  90. status[0] = 500; body[0] = "5"; check("HTTP 500 -> UNKNOWN", communications.getPlaytimeFromServer(me, "g") == communications.UNKNOWN);
  91. status[0] = 200; body[0] = "Max play time exceeded."; check("text reply -> 0", communications.getPlaytimeFromServer(me, "g") == 0);
  92. body[0] = "x\"; touch /tmp/pwned; \""; check("hostile text reply -> 0, no command run", communications.getPlaytimeFromServer(me, "g") == 0 && !new java.io.File("/tmp/pwned").exists());
  93. // games list: good, then server error keeps old list, empty answer keeps old list
  94. status[0] = 200; body[0] = "[\"minecraft\",\"steam\"]";
  95. check("games fetched", communications.getGamesListFromServer() && Main.GamesList.length == 2);
  96. status[0] = 500;
  97. check("server error keeps list", !communications.getGamesListFromServer() && Main.GamesList.length == 2);
  98. status[0] = 200; body[0] = "[]";
  99. check("empty answer keeps list", !communications.getGamesListFromServer() && Main.GamesList.length == 2);
  100. Main.GamesList = new String[0];
  101. status[0] = 500;
  102. communications.getGamesListFromServer();
  103. check("offline start uses cache", Main.GamesList.length == 2);
  104. // unreachable server
  105. server.stop(0);
  106. long t1 = System.currentTimeMillis();
  107. check("unreachable -> UNKNOWN fast", communications.getPlaytimeFromServer(me, "g") == communications.UNKNOWN && System.currentTimeMillis() - t1 < 15000);
  108. stubborn.destroyForcibly();
  109. System.out.println(failures == 0 ? "ALL PASSED" : failures + " FAILED");
  110. System.exit(failures == 0 ? 0 : 1);
  111. }
  112. }