/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.sling.launchpad.app; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.util.Collections; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.sling.launchpad.base.shared.SharedConstants; import org.junit.Assert; import junit.framework.TestCase; import junitx.util.PrivateAccessor; public class ControlListenerTest extends TestCase { private static String SLING1 = "target/sling.test/1"; private static String SLING2 = "target/sling.test/2"; private static String CTL = "conf/controlport"; private final File ctlFile1 = new File(SLING1, CTL); private final File ctlFile2 = new File(SLING2, CTL); @Override protected void tearDown() throws Exception { ctlFile1.delete(); ctlFile2.delete(); super.tearDown(); } public void test_start_status_stop() { int port = getPort(); MyMain main = new MyMain(SLING1); ControlListener cl = new ControlListener(main, String.valueOf(port)); TestCase.assertTrue(cl.listen()); delay(); // wait for sever to start TestCase.assertTrue(ctlFile1.canRead()); TestCase.assertEquals(0, new ControlListener(main, null).statusServer()); TestCase.assertEquals(0, new ControlListener(main, null).shutdownServer()); delay(); // wait for server to stop TestCase.assertTrue(main.stopCalled); delay(); TestCase.assertFalse(ctlFile1.exists()); } public void test_start_status_long_stop() { int port = getPort(); MyMain2 main = new MyMain2(SLING1); ControlListener cl = new ControlListener(main, String.valueOf(port)); TestCase.assertTrue(cl.listen()); delay(); // wait for sever to start TestCase.assertTrue(ctlFile1.canRead()); TestCase.assertEquals(0, new ControlListener(main, null).statusServer()); TestCase.assertEquals(0, new ControlListener(main, null).shutdownServer()); delay(); // wait for server to stop TestCase.assertTrue(main.stopCalled); TestCase.assertEquals(0, new ControlListener(main, null).statusServer()); Thread t1 = new MonitorDeadlockingThread(false); Thread t2 = new MonitorDeadlockingThread(true); t1.start(); t2.start(); Thread t3 = new SynchronizerDeadlockingThread(false); Thread t4 = new SynchronizerDeadlockingThread(true); t3.start(); t4.start(); try { Thread.sleep(200); } catch (InterruptedException ie) { } TestCase.assertEquals(0, new ControlListener(main, null).dumpThreads()); TestCase.assertEquals(0, new ControlListener(main, null).shutdownServer()); // make sure it will shutdown now t1.interrupt(); t2.interrupt(); t3.interrupt(); t4.interrupt(); main.trigger(); delay(); TestCase.assertFalse(ctlFile1.exists()); } static class MonitorDeadlockingThread extends Thread { private static final Object l1 = new Object(); private static final Object l2 = new Object(); private final boolean flag; MonitorDeadlockingThread(boolean flag) { this.flag = flag; this.setDaemon(true); } @Override public void run() { if (this.flag) { synchronized (l1) { try { Thread.sleep(50); } catch (InterruptedException ie) { } synchronized(l2) { System.out.println(Thread.currentThread().getName() + ": locked l2"); } } } else { synchronized(l2) { try { Thread.sleep(50); } catch (InterruptedException ie) { } synchronized (l1) { System.out.println(Thread.currentThread().getName() + ": locked l1"); } } } } } static class SynchronizerDeadlockingThread extends Thread { private static final ReentrantLock l1 = new ReentrantLock(); private static final ReentrantLock l2 = new ReentrantLock(); private final boolean flag; SynchronizerDeadlockingThread(boolean flag) { this.flag = flag; this.setDaemon(true); } @Override public void run() { if (this.flag) { try { l1.lock(); try { Thread.sleep(100); } catch (InterruptedException ie) { } l2.lock(); } finally { l2.unlock(); l1.unlock(); } } else { try { l2.lock(); try { Thread.sleep(100); } catch (InterruptedException ie) { } l1.lock(); } finally { l1.unlock(); l2.unlock(); } } } } public void test_parallel_start_status_stop() { int port1 = getPort(); MyMain main1 = new MyMain(SLING1); ControlListener server1 = new ControlListener(main1, String.valueOf(port1)); TestCase.assertTrue(server1.listen()); delay(); // wait for sever to start int port2 = getPort(); MyMain main2 = new MyMain(SLING2); ControlListener server2 = new ControlListener(main2, String.valueOf(port2)); TestCase.assertTrue(server2.listen()); delay(); // wait for sever to start TestCase.assertTrue(ctlFile1.canRead()); TestCase.assertTrue(ctlFile2.canRead()); TestCase.assertEquals(0, new ControlListener(main1, null).statusServer()); TestCase.assertEquals(0, new ControlListener(main1, null).shutdownServer()); delay(); // wait for sever to stop TestCase.assertEquals(0, new ControlListener(main2, null).statusServer()); TestCase.assertEquals(0, new ControlListener(main2, null).shutdownServer()); delay(); // wait for sever to stop TestCase.assertTrue(main1.stopCalled); TestCase.assertTrue(main2.stopCalled); delay(); TestCase.assertFalse(ctlFile1.exists()); TestCase.assertFalse(ctlFile2.exists()); } public void test_no_start_in_same_sling_home() { int port1 = getPort(); MyMain main1 = new MyMain(SLING1); ControlListener server1 = new ControlListener(main1, String.valueOf(port1)); TestCase.assertTrue(server1.listen()); delay(); // wait for sever to start int port2 = getPort(); MyMain main2 = new MyMain(SLING1); ControlListener server2 = new ControlListener(main2, String.valueOf(port2)); TestCase.assertFalse(server2.listen()); delay(); // wait for sever to start TestCase.assertTrue(ctlFile1.canRead()); TestCase.assertFalse(ctlFile2.canRead()); TestCase.assertEquals(0, new ControlListener(main1, null).statusServer()); TestCase.assertEquals(0, new ControlListener(main1, null).shutdownServer()); delay(); // wait for server to stop TestCase.assertTrue(main1.stopCalled); delay(); TestCase.assertFalse(ctlFile1.exists()); // retry cl2 TestCase.assertTrue(server2.listen()); delay(); // wait for sever to start TestCase.assertEquals(0, new ControlListener(main2, null).statusServer()); TestCase.assertEquals(0, new ControlListener(main2, null).shutdownServer()); delay(); // wait for server to stop TestCase.assertTrue(main2.stopCalled); delay(); TestCase.assertFalse(ctlFile2.exists()); } public void test_no_status() throws IOException { int port = getPort(); MyMain main = new MyMain(SLING1); TestCase.assertFalse(ctlFile1.exists()); ctlFile1.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(ctlFile1); PrintWriter pw = new PrintWriter(out); pw.println("127.0.0.1:" + port); pw.println("password"); pw.close(); TestCase.assertTrue(new File(SLING1, CTL).canRead()); TestCase.assertEquals(3, new ControlListener(main, null).statusServer()); TestCase.assertEquals(3, new ControlListener(main, null).shutdownServer()); delay(); // wait for server to stop TestCase.assertFalse(main.stopCalled); TestCase.assertTrue(ctlFile1.exists()); } public void test_no_configuration_for_status() { int port = getPort(); MyMain main = new MyMain(SLING1); TestCase.assertFalse(ctlFile1.exists()); TestCase.assertEquals(4, new ControlListener(main, String.valueOf(port)).statusServer()); TestCase.assertEquals(4, new ControlListener(main, String.valueOf(port)).shutdownServer()); delay(); // wait for server to stop TestCase.assertFalse(main.stopCalled); TestCase.assertFalse(ctlFile1.exists()); } public void test_no_key_for_status() throws IOException { int port = getPort(); MyMain main = new MyMain(SLING1); TestCase.assertFalse(ctlFile1.exists()); ctlFile1.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(ctlFile1); PrintWriter pw = new PrintWriter(out); pw.println("127.0.0.1:" + port); pw.close(); TestCase.assertTrue(new File(SLING1, CTL).canRead()); TestCase.assertEquals(4, new ControlListener(main, null).statusServer()); TestCase.assertEquals(4, new ControlListener(main, null).shutdownServer()); delay(); // wait server to stop TestCase.assertFalse(main.stopCalled); TestCase.assertTrue(ctlFile1.exists()); } public void test_generateKey() throws Throwable { Pattern pattern = Pattern.compile("([a-zA-Z0-9-_=]+)"); MyMain main = new MyMain(SLING1); ControlListener cl = new ControlListener(main, null); String secretkey = (String) PrivateAccessor.invoke(cl, "generateKey", new Class[] {}, new Object[] {}); Assert.assertTrue(secretkey.length() >= 32); Matcher matcher = pattern.matcher(secretkey); if (!matcher.matches()) { Assert.fail(); } } //-------------------- private section ----------------------------- private int getPort() { ServerSocket s = null; try { s = new ServerSocket(0); return s.getLocalPort(); } catch (IOException ignore) { } finally { if (s != null) { try { s.close(); } catch (IOException i2) { } } } TestCase.fail("Cannot acquire port"); return 0; // compiler satisfaction } private void delay() { try { Thread.sleep(200); } catch (InterruptedException ie) { } } private static class MyMain extends Main { volatile boolean stopCalled; MyMain(final String slingHome) { super(Collections.singletonMap(SharedConstants.SLING_HOME, slingHome)); } @Override protected void doStop() { this.stopCalled = true; } @Override void terminateVM(int status) { // not really } } private static class MyMain2 extends MyMain { private final Object trigger = new Object(); public MyMain2(final String slingHome) { super(slingHome); } @Override protected void doStop() { super.doStop(); // wait for trigger to continue simlutating a blocked // shutdown -- at most 10 seconds, though synchronized (this.trigger) { try { this.trigger.wait(10 * 1000L); } catch (InterruptedException e) { // ignore and just continue } } } void trigger() { synchronized (this.trigger) { this.trigger.notifyAll(); } } } }