/* TestTCFMemoryV2.java (c) 2011-2013 Edward Swartz All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html */ package v9t9.server.tcf.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.tm.tcf.protocol.IToken; import org.eclipse.tm.tcf.protocol.Protocol; import org.eclipse.tm.tcf.services.IMemory; import org.eclipse.tm.tcf.services.IMemory.MemoryError; import org.junit.Before; import org.junit.Test; import ejs.base.utils.Pair; import v9t9.common.memory.IMemoryDomain; import v9t9.server.tcf.services.IMemoryV2; import v9t9.server.tcf.services.IMemoryV2.MemoryChange; /** * @author ejs * */ public class TestTCFMemoryV2 extends BaseTCFTest { private IMemoryV2 memV2; @Before public void getServices() { memV2 = (IMemoryV2) getService(IMemoryV2.NAME); } static class MemListener implements IMemoryV2.MemoryContentChangeListener { final List<Pair<String, MemoryChange[]>> changeMap = new ArrayList<Pair<String, MemoryChange[]>>(); @Override public void contentChanged(String notifyId, MemoryChange[] change) { changeMap.add(new Pair<String, MemoryChange[]>(notifyId, change)); } }; @Test public void testMemoryNotifySimple() throws Throwable { // start... final int DELAY = 100; int memMode = 0; final MemListener listener = new MemListener(); doVideoMemWrite(memMode, DELAY, "hello", listener, "HELLO"); assertTrue("expected event", !listener.changeMap.isEmpty()); // expect only one change and one range assertEquals(1, listener.changeMap.size()); assertEquals("hello", listener.changeMap.get(0).first); MemoryChange[] changes = listener.changeMap.get(0).second; assertNotNull(changes); assertEquals(1, changes.length); MemoryChange change = changes[0]; assertEquals(0, change.addr); assertEquals(5, change.size); assertTrue(new String(change.data), Arrays.equals("HELLO".getBytes(), change.data)); } @Test public void testMemoryNoNotifySimple() throws Throwable { final int DELAY = 100; final int memMode = IMemoryV2.MODE_FLAT; // no events should be generated final MemListener listener = new MemListener(); doVideoMemWrite(memMode, DELAY, "none", listener, "blargh"); assertTrue("expected no events: " + listener.changeMap, listener.changeMap.isEmpty()); } protected void doVideoMemWrite(final int memMode, final int delay, final String notifyId, final MemListener listener, final String data) throws Throwable { doVideoMemWrite(memMode, notifyId, delay, 1, listener, data.getBytes(), 0, data.length(), 0, 0); } /** * @param memMode * @param notifyId TODO * @param delay * @param granularity * @param listener * @param method * @throws Throwable */ protected void doVideoMemWrite(final int memMode, final String notifyId, final int delay, final int granularity, final MemListener listener, final byte[] data, final int addr_, final int size, final int skip, final int skipLength) throws Throwable { Protocol.invokeAndWait(new Runnable() { public void run() { memV2.addListener(listener); } }); final IMemory.MemoryContext video = getMemoryContext(memV2, IMemoryDomain.NAME_VIDEO); // save... final byte[] orig = new byte[size + 4]; new TCFCommandWrapper() { public IToken run() throws Exception { return video.get((Integer) (addr_ - 2), 1, orig, 0, size + 4, 0, new IMemoryV2.DoneMemory() { @Override public void doneMemory(IToken token, MemoryError error) { try { assertNoError(error); } catch (Throwable t) { excs[0] = t; } finally { tcfDone(); } } }); } }; try { new TCFCommandWrapper() { public IToken run() throws Exception { return memV2.startChangeNotify( notifyId, IMemoryDomain.NAME_VIDEO, addr_, size, delay, granularity, new IMemoryV2.DoneCommand() { @Override public void done(Exception error) { try { assertNoError(error); } catch (Throwable t) { excs[0] = t; } finally { tcfDone(); } } }); } }; // we should get one initial contentChanged report for the full range validateInitialContentChanged(notifyId, addr_, size, granularity, listener.changeMap); // this set tracks outstanding Memory#set events final boolean[] finished = { true }; final Set<IToken> waiting = new HashSet<IToken>(); final IMemory.DoneMemory done = new IMemory.DoneMemory() { @Override public void doneMemory(IToken token, MemoryError error) { assertNoError(error); synchronized (waiting) { waiting.remove(token); if (waiting.isEmpty()) finished[0] = true; } } }; int addr = addr_; int idx = 0; int end = addr + size; while (addr < end) { if (idx >= data.length) idx = 0; final int theAddr = addr; final int theIdx = idx; int toUse; toUse = Math.min(end - addr, Math.min(data.length - idx, size - idx)); if (skip > 0) toUse = Math.min(skip, toUse); final int toUse_ = toUse; System.out.println("set: " + theAddr + "+" + toUse + " @ " + theIdx); new TCFCommandWrapper() { public IToken run() throws Exception { try { IToken token = video.set(Integer.valueOf(theAddr), 1, data, theIdx, toUse_, memMode, done); synchronized (waiting) { waiting.add(token); } return token; } finally { tcfDone(); } } }; addr += toUse; idx += toUse; if (skip > 0) { if (toUse < skip) { // use more next time idx = 0; } else { addr += skipLength; } } else { if (toUse < size) idx = 0; } } // set outside range if (addr_ >= 2) { new TCFCommandWrapper() { public IToken run() throws Exception { try { IToken token = video.set(Integer.valueOf(addr_ - 2), 1, new byte[] { '?', '!' }, 0, 2, memMode, done); synchronized (waiting) { waiting.add(token); } return token; } finally { tcfDone(); } } }; } new TCFCommandWrapper() { public IToken run() throws Exception { try { IToken token = video.set(Integer.valueOf(addr_ + size), 1, new byte[] { '?', '!' }, 0, 2, memMode, done); synchronized (waiting) { waiting.add(token); } return token; } finally { tcfDone(); } } }; long timeout = System.currentTimeMillis() + 10 * 1000; while (!finished[0]) { if (System.currentTimeMillis() > timeout) fail("timed out waiting for memory writes"); Thread.sleep(500); } new TCFCommandWrapper() { public IToken run() throws Exception { return memV2.stopChangeNotify(notifyId, new IMemoryV2.DoneCommand() { @Override public void done(Exception error) { try { assertNoError(error); } catch (Throwable t) { excs[0] = t; } finally { tcfDone(); } } }); } }; // await the event Thread.sleep(delay * 5); } finally { Protocol.invokeAndWait(new Runnable() { public void run() { memV2.removeListener(listener); } }); // restore... new TCFCommandWrapper() { public IToken run() throws Exception { return video.set((Integer) (addr_ - 2), 1, orig, 0, (size + 4), 0, new IMemoryV2.DoneMemory() { @Override public void doneMemory(IToken token, MemoryError error) { try { assertNoError(error); } catch (Throwable t) { excs[0] = t; } finally { tcfDone(); } } }); } }; } } /** * @param changeMap * @param i * @param j */ private void validateInitialContentChanged(String expId, int addr, int size, int granularity, List<Pair<String, MemoryChange[]>> changeMap) { assertEquals("initial change", 1, changeMap.size()); Pair<String, MemoryChange[]> ent = changeMap.get(0); assertEquals(expId, ent.first); assertEquals("initial change", 1, ent.second.length); for (MemoryChange change : ent.second) { int alignedAddr = addr & -granularity; int alignedSize = (addr + size) - alignedAddr; alignedSize = (alignedSize + granularity - 1) & -granularity; assertEquals("initial change", alignedAddr, change.addr); assertEquals("initial change", alignedSize, change.size); assertEquals(change.toString(), change.size, change.data.length); } changeMap.clear(); } /** * @param changeMap * @param i * @param j */ private void validateRanges(String expId, byte[] data, int addr, int size, int skip, int skipLength, List<Pair<String, MemoryChange[]>> changeMap) { // ensure the pairs are all contiguous for (Pair<String, MemoryChange[]> ent : changeMap) { Arrays.sort(ent.second, new Comparator<MemoryChange>() { @Override public int compare(MemoryChange o1, MemoryChange o2) { return o1.addr.intValue() - o2.addr.intValue(); } }); } // ensure each piece is in order Collections.sort(changeMap, new Comparator<Pair<String, MemoryChange[]>>() { @Override public int compare(Pair<String, MemoryChange[]> o1, Pair<String, MemoryChange[]> o2) { return o1.second[0].addr.intValue() - o2.second[0].addr.intValue(); } }); //int end = addr + size; int idx = 0; for (Pair<String, MemoryChange[]> ent : changeMap) { assertEquals(expId, ent.first); for (MemoryChange change : ent.second) { assertEquals(addr, change.addr); //assertEquals(change.toString(), skip > 0 ? Math.min(end - addr, skip) : end - addr, change.size); assertEquals(change.toString(), change.size, change.data.length); if (change.size < skipLength) addr += change.size; else addr += change.size + skipLength; idx = (idx + (int) change.size) % data.length; } } changeMap.clear(); } @Test public void testMemoryNotifyScatter() throws Throwable { // start... final int DELAY = 100; int memMode = 0; final MemListener listener = new MemListener(); byte[] data = { 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; // write 377 to 768 by 2 doVideoMemWrite(memMode, "scatter1", DELAY, 1, listener, data, 377, 768 - 377, 2, 1); assertTrue("expected event", !listener.changeMap.isEmpty()); validateRanges("scatter1", data, 377, 768 - 377, 2, 1, listener.changeMap); // write 377 to 768 by 2, no gaps doVideoMemWrite(memMode, "scatter2", DELAY, 2, listener, data, 377, 768 - 377, 2, 1); assertTrue("expected event", !listener.changeMap.isEmpty()); validateRanges("scatter2", data, 376, 768 - 376, 0, 0, listener.changeMap); // huge chunk doVideoMemWrite(memMode, "scatter3", DELAY, 1024, listener, data, 123, 15, 2, 7); assertTrue("expected event", !listener.changeMap.isEmpty()); validateRanges("scatter3", data, 0, 1024, 0, 0, listener.changeMap); } }