aboutsummaryrefslogtreecommitdiff
path: root/app/src/test/java/invalid
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/test/java/invalid')
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/AtomicFilesTest.java12
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/AudioStreamTest.java13
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/ControlMessagesTest.java29
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/ControlStreamTest.java10
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/CrashlogTest.java36
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/DevicesTest.java16
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/TouchGeometryTest.java48
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/TouchMapTest.java85
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/VideoQueueTest.java53
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/VideoStreamTest.java61
-rw-r--r--app/src/test/java/invalid/lena/scrcpy/WireTest.java58
11 files changed, 339 insertions, 82 deletions
diff --git a/app/src/test/java/invalid/lena/scrcpy/AtomicFilesTest.java b/app/src/test/java/invalid/lena/scrcpy/AtomicFilesTest.java
index 7124fd2..1cafb0c 100644
--- a/app/src/test/java/invalid/lena/scrcpy/AtomicFilesTest.java
+++ b/app/src/test/java/invalid/lena/scrcpy/AtomicFilesTest.java
@@ -51,4 +51,16 @@ public class AtomicFilesTest {
assertArrayEquals("good2".getBytes(), Files.readAllBytes(f.toPath()));
assertFalse(new File(dir.getRoot(), "x.tmp").exists());
}
+
+ @Test
+ public void writeRemovesEveryStaleTemp() throws Exception {
+ File f = new File(dir.getRoot(), "x");
+ Files.write(new File(dir.getRoot(), "x.tmp-old").toPath(), new byte[]{1});
+ Files.write(new File(dir.getRoot(), "x.tmp-new").toPath(), new byte[]{2});
+
+ AtomicFiles.write(f, "good".getBytes());
+
+ assertFalse(new File(dir.getRoot(), "x.tmp-old").exists());
+ assertFalse(new File(dir.getRoot(), "x.tmp-new").exists());
+ }
}
diff --git a/app/src/test/java/invalid/lena/scrcpy/AudioStreamTest.java b/app/src/test/java/invalid/lena/scrcpy/AudioStreamTest.java
index 076d27c..005a2e3 100644
--- a/app/src/test/java/invalid/lena/scrcpy/AudioStreamTest.java
+++ b/app/src/test/java/invalid/lena/scrcpy/AudioStreamTest.java
@@ -11,6 +11,7 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
public class AudioStreamTest {
@@ -116,8 +117,18 @@ public class AudioStreamTest {
public void errorCodecDoesNotStart() throws Exception {
byte[] bytes = fourcc(1);
RecordingFrames sink = new RecordingFrames();
- new AudioStream(new ByteArrayInputStream(bytes), sink).run();
+ AtomicInteger fatal = new AtomicInteger();
+ new AudioStream(new ByteArrayInputStream(bytes), sink, fatal::incrementAndGet).run();
assertEquals(0, sink.starts);
assertEquals(0, sink.feeds.size());
+ assertEquals(1, fatal.get());
+ }
+
+ @Test
+ public void disabledCodecIsNotFatal() throws Exception {
+ AtomicInteger fatal = new AtomicInteger();
+ new AudioStream(new ByteArrayInputStream(fourcc(0)),
+ new RecordingFrames(), fatal::incrementAndGet).run();
+ assertEquals(0, fatal.get());
}
}
diff --git a/app/src/test/java/invalid/lena/scrcpy/ControlMessagesTest.java b/app/src/test/java/invalid/lena/scrcpy/ControlMessagesTest.java
index 1890860..142bb34 100644
--- a/app/src/test/java/invalid/lena/scrcpy/ControlMessagesTest.java
+++ b/app/src/test/java/invalid/lena/scrcpy/ControlMessagesTest.java
@@ -16,6 +16,28 @@ public class ControlMessagesTest {
}
@Test
+ public void clipboardLimitsMatchScrcpyMessageSize() {
+ // The server caps a whole control message at MESSAGE_MAX_SIZE and
+ // derives each direction's text limit by subtracting that
+ // direction's header. Derive it the same way rather than copying
+ // the numbers, so a server bump shows up here as a failure.
+ final int messageMax = 1 << 18; // 256 KiB
+ assertEquals(messageMax - (1 + 8 + 1 + 4), // SET_CLIPBOARD
+ ControlMessages.MAX_CLIPBOARD_BYTES);
+ assertEquals(messageMax - (1 + 4), // DeviceMessage
+ ControlMessages.MAX_DEVICE_CLIPBOARD_BYTES);
+ }
+
+ @Test
+ public void clipboardAcceptsExactlyTheLimit() {
+ char[] chars = new char[ControlMessages.MAX_CLIPBOARD_BYTES];
+ java.util.Arrays.fill(chars, 'a'); // 1 byte per char in UTF-8
+ byte[] m = ControlMessages.setClipboard(0, false, new String(chars));
+ assertEquals(14 + ControlMessages.MAX_CLIPBOARD_BYTES, m.length);
+ assertEquals(ControlMessages.MAX_CLIPBOARD_BYTES, Wire.readBe32(m, 10));
+ }
+
+ @Test
public void touchByteLayout() {
// ACTION_DOWN=0, single finger, target 1080x2400, x=100, y=200, full pressure
byte[] m = ControlMessages.touch(/* action */ 0, /* pointerId */ 0L,
@@ -47,8 +69,13 @@ public class ControlMessagesTest {
}
@Test
+ public void resetVideoByteLayout() {
+ assertArrayEquals(new byte[]{17}, ControlMessages.resetVideo());
+ }
+
+ @Test
public void setClipboardWithUtf8Payload() {
- String text = "héllo 🎉"; // includes a 4-byte surrogate pair
+ String text = "héllo \ud83c\udf89"; // includes a 4-byte surrogate pair
byte[] expected = text.getBytes(StandardCharsets.UTF_8);
byte[] m = ControlMessages.setClipboard(0xdeadbeefcafeL, /* paste */ true, text);
assertEquals(1 + 8 + 1 + 4 + expected.length, m.length);
diff --git a/app/src/test/java/invalid/lena/scrcpy/ControlStreamTest.java b/app/src/test/java/invalid/lena/scrcpy/ControlStreamTest.java
index 8573d3b..f3359e4 100644
--- a/app/src/test/java/invalid/lena/scrcpy/ControlStreamTest.java
+++ b/app/src/test/java/invalid/lena/scrcpy/ControlStreamTest.java
@@ -36,7 +36,7 @@ public class ControlStreamTest {
RecordingSink sink = new RecordingSink();
ControlStream cs = new ControlStream(
new ByteArrayInputStream(bos.toByteArray()),
- new ByteArrayOutputStream());
+ new ByteArrayOutputStream(), null);
cs.setInboundSink(sink);
cs.runReader(); // synchronous; returns on EOF
@@ -55,7 +55,7 @@ public class ControlStreamTest {
RecordingSink sink = new RecordingSink();
ControlStream cs = new ControlStream(
new ByteArrayInputStream(bos.toByteArray()),
- new ByteArrayOutputStream());
+ new ByteArrayOutputStream(), null);
cs.setInboundSink(sink);
cs.runReader();
@@ -73,7 +73,7 @@ public class ControlStreamTest {
RecordingSink sink = new RecordingSink();
ControlStream cs = new ControlStream(
new ByteArrayInputStream(bos.toByteArray()),
- new ByteArrayOutputStream());
+ new ByteArrayOutputStream(), null);
cs.setInboundSink(sink);
cs.runReader();
assertEquals(0, sink.clipboards.size());
@@ -87,7 +87,7 @@ public class ControlStreamTest {
// outbox is empty AND we've signalled stop.
ByteArrayOutputStream out = new ByteArrayOutputStream();
ControlStream cs = new ControlStream(
- new ByteArrayInputStream(new byte[0]), out);
+ new ByteArrayInputStream(new byte[0]), out, null);
byte[] a = new byte[]{1, 2, 3};
byte[] b = new byte[]{4, 5};
@@ -118,7 +118,7 @@ public class ControlStreamTest {
// event must be preserved and end up last in the drain.
ByteArrayOutputStream out = new ByteArrayOutputStream();
ControlStream cs = new ControlStream(
- new ByteArrayInputStream(new byte[0]), out);
+ new ByteArrayInputStream(new byte[0]), out, null);
for (int i = 0; i < 256; i++) {
cs.send(ControlMessages.touch(/* MOVE */ 2, 0L, i, i, 1080, 2400, 0xffff, 0, 0));
diff --git a/app/src/test/java/invalid/lena/scrcpy/CrashlogTest.java b/app/src/test/java/invalid/lena/scrcpy/CrashlogTest.java
new file mode 100644
index 0000000..41c05a1
--- /dev/null
+++ b/app/src/test/java/invalid/lena/scrcpy/CrashlogTest.java
@@ -0,0 +1,36 @@
+package invalid.lena.scrcpy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.nio.file.Files;
+
+public class CrashlogTest {
+
+ @Rule
+ public TemporaryFolder dir = new TemporaryFolder();
+
+ @Test
+ public void pruneKeepsNewestCrashlogsOnly() throws Exception {
+ for (int i = 0; i < 7; i++) {
+ File file = new File(dir.getRoot(), "crash-" + i + ".log");
+ Files.write(file.toPath(), new byte[]{(byte) i});
+ assertTrue(file.setLastModified(1_000L + i));
+ }
+ File unrelated = dir.newFile("notes.txt");
+
+ Crashlog.prune(dir.getRoot(), 5);
+
+ File[] logs = dir.getRoot().listFiles((parent, name) -> name.endsWith(".log"));
+ assertEquals(5, logs.length);
+ assertFalse(new File(dir.getRoot(), "crash-0.log").exists());
+ assertFalse(new File(dir.getRoot(), "crash-1.log").exists());
+ assertTrue(unrelated.exists());
+ }
+}
diff --git a/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java b/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java
index dcd04ca..bde8af8 100644
--- a/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java
+++ b/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java
@@ -2,6 +2,7 @@ package invalid.lena.scrcpy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
@@ -20,9 +21,10 @@ public class DevicesTest {
}
@Test
- public void parseNonArrayReturnsEmpty() {
- assertTrue(Devices.parse("{not json").isEmpty());
- assertTrue(Devices.parse("{\"host\":\"x\"}").isEmpty());
+ public void parseNonArrayFailsLoudly() {
+ assertThrows(IllegalArgumentException.class, () -> Devices.parse("{not json"));
+ assertThrows(IllegalArgumentException.class,
+ () -> Devices.parse("{\"host\":\"x\"}"));
}
@Test
@@ -54,6 +56,14 @@ public class DevicesTest {
}
@Test
+ public void serializeRejectsInvalidRows() {
+ assertThrows(IllegalStateException.class, () -> Devices.serialize(Arrays.asList(
+ new Devices.Device("bad host", 5555))));
+ assertThrows(IllegalStateException.class, () -> Devices.serialize(Arrays.asList(
+ new Devices.Device("target", 0))));
+ }
+
+ @Test
public void parseAddressSplitsHostAndPort() {
Devices.Device d = Devices.parseAddress(" 192.168.1.42:41234 ");
assertEquals("192.168.1.42", d.host);
diff --git a/app/src/test/java/invalid/lena/scrcpy/TouchGeometryTest.java b/app/src/test/java/invalid/lena/scrcpy/TouchGeometryTest.java
new file mode 100644
index 0000000..5db6d32
--- /dev/null
+++ b/app/src/test/java/invalid/lena/scrcpy/TouchGeometryTest.java
@@ -0,0 +1,48 @@
+package invalid.lena.scrcpy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class TouchGeometryTest {
+
+ @Test
+ public void viewportBeforeTargetDoesNotEnableInput() {
+ TouchGeometry geometry = new TouchGeometry();
+ geometry.setViewport(0, 0, 0, 1080, 608);
+ assertNull(geometry.snapshot());
+ }
+
+ @Test
+ public void targetResizeInvalidatesViewport() {
+ TouchGeometry geometry = new TouchGeometry();
+ long first = 7;
+ geometry.setTargetSize(first, 1920, 1080);
+ geometry.setViewport(first, 0, 100, 1080, 608);
+
+ long second = 8;
+ geometry.setTargetSize(second, 1080, 1920);
+
+ assertNull(geometry.snapshot());
+ geometry.setViewport(first, 100, 0, 608, 1080);
+ assertNull(geometry.snapshot());
+
+ geometry.setViewport(second, 100, 0, 608, 1080);
+ TouchGeometry.Snapshot snapshot = geometry.snapshot();
+ assertEquals(1080, snapshot.targetW);
+ assertEquals(1920, snapshot.targetH);
+ assertEquals(100, snapshot.x);
+ assertEquals(608, snapshot.w);
+ }
+
+ @Test
+ public void emptyViewportDisablesInput() {
+ TouchGeometry geometry = new TouchGeometry();
+ long version = 1;
+ geometry.setTargetSize(version, 1920, 1080);
+ geometry.setViewport(version, 0, 0, 1080, 608);
+ geometry.setViewport(version, 0, 0, 0, 0);
+ assertNull(geometry.snapshot());
+ }
+}
diff --git a/app/src/test/java/invalid/lena/scrcpy/TouchMapTest.java b/app/src/test/java/invalid/lena/scrcpy/TouchMapTest.java
new file mode 100644
index 0000000..becacc1
--- /dev/null
+++ b/app/src/test/java/invalid/lena/scrcpy/TouchMapTest.java
@@ -0,0 +1,85 @@
+package invalid.lena.scrcpy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+// The arithmetic that decides where a tap lands. Until this existed the
+// mapping was verified by nothing: the only coverage was an emulator
+// self-mirror, where the target and the view are the same size, so the
+// transform is the identity and every scaling or offset bug passes.
+public class TouchMapTest {
+
+ // A 1080x2400 phone showing a 1080x1920 target: fitted to 864x1920,
+ // leaving 108px bars left and right.
+ private static final int VIEW_X = 108, VIEW_W = 864, TARGET_W = 1080;
+
+ @Test
+ public void leftEdgeOfTheVideoIsTheLeftEdgeOfTheTarget() {
+ assertEquals(0, TouchMap.map(VIEW_X, VIEW_X, VIEW_W, TARGET_W));
+ }
+
+ // The view has fewer pixels than the target, so the last column the
+ // mapping can produce is floor((span-1) * target / span) - 1078 here,
+ // not 1079. That is granularity, not an off-by-one: a 864px-wide view
+ // cannot address all 1080 target columns. What matters is that the
+ // right edge lands on the last addressable column and stays inside.
+ @Test
+ public void rightEdgeIsTheLastAddressableColumn() {
+ int got = TouchMap.map(VIEW_X + VIEW_W - 1, VIEW_X, VIEW_W, TARGET_W);
+ assertEquals((VIEW_W - 1) * TARGET_W / VIEW_W, got);
+ assertTrue("must stay inside the target", got < TARGET_W);
+ assertTrue("must be within one step of the far edge",
+ got >= TARGET_W - (TARGET_W / VIEW_W) - 1);
+ }
+
+ @Test
+ public void centreMapsToCentre() {
+ assertEquals(TARGET_W / 2, TouchMap.map(VIEW_X + VIEW_W / 2, VIEW_X, VIEW_W, TARGET_W));
+ }
+
+ @Test
+ public void theLetterboxBarIsClampedNotWrapped() {
+ // Touches on the bars must land on the near edge. Subtracting the
+ // origin without clamping would make the left bar negative and the
+ // right bar overflow past the target's width.
+ assertEquals(0, TouchMap.map(0, VIEW_X, VIEW_W, TARGET_W));
+ assertEquals(0, TouchMap.map(VIEW_X - 50, VIEW_X, VIEW_W, TARGET_W));
+ int far = (VIEW_W - 1) * TARGET_W / VIEW_W; // last addressable column
+ assertEquals(far, TouchMap.map(VIEW_X + VIEW_W + 50, VIEW_X, VIEW_W, TARGET_W));
+ assertEquals(far, TouchMap.map(2000, VIEW_X, VIEW_W, TARGET_W));
+ }
+
+ @Test
+ public void neverEscapesTheTargetForAnyCoordinate() {
+ for (int c = -500; c < 3000; c++) {
+ int t = TouchMap.map(c, VIEW_X, VIEW_W, TARGET_W);
+ if (t < 0 || t >= TARGET_W) {
+ throw new AssertionError("coord " + c + " mapped outside the target: " + t);
+ }
+ }
+ }
+
+ @Test
+ public void noOverflowAtFourK() {
+ // 3840 * 2160 exceeds int when multiplied in 32 bits; the mapping
+ // has to widen before it scales.
+ assertEquals(2159, TouchMap.map(3839, 0, 3840, 2160));
+ assertEquals(0, TouchMap.map(0, 0, 3840, 2160));
+ }
+
+ @Test
+ public void identityWhenTheViewMatchesTheTarget() {
+ // The self-mirror case the e2e exercises: nothing should move.
+ for (int c : new int[]{0, 1, 539, 1079}) {
+ assertEquals(c, TouchMap.map(c, 0, 1080, 1080));
+ }
+ }
+
+ @Test
+ public void degenerateGeometryIsRefusedRatherThanDividingByZero() {
+ assertEquals(0, TouchMap.map(100, 0, 0, 1080));
+ assertEquals(0, TouchMap.map(100, 0, 1080, 0));
+ }
+}
diff --git a/app/src/test/java/invalid/lena/scrcpy/VideoQueueTest.java b/app/src/test/java/invalid/lena/scrcpy/VideoQueueTest.java
new file mode 100644
index 0000000..00b19a5
--- /dev/null
+++ b/app/src/test/java/invalid/lena/scrcpy/VideoQueueTest.java
@@ -0,0 +1,53 @@
+package invalid.lena.scrcpy;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class VideoQueueTest {
+
+ private static VideoQueue.Frame frame(int id, boolean config, boolean keyframe) {
+ return new VideoQueue.Frame(new byte[]{(byte) id}, id, config, keyframe);
+ }
+
+ @Test
+ public void rejectsDeltaUntilKeyframe() {
+ VideoQueue queue = new VideoQueue(4, 4);
+ assertFalse(queue.offer(frame(1, false, false)));
+ assertTrue(queue.offer(frame(2, false, true)));
+ assertTrue(queue.offer(frame(3, false, false)));
+ }
+
+ @Test
+ public void overflowKeepsQueuedDecoderGeneration() {
+ VideoQueue queue = new VideoQueue(3, 3);
+ VideoQueue.Frame keyframe = frame(1, false, true);
+ VideoQueue.Frame delta = frame(2, false, false);
+ assertTrue(queue.offer(keyframe));
+ assertTrue(queue.offer(delta));
+ assertTrue(queue.offer(frame(3, false, false)));
+ assertFalse(queue.offer(frame(4, false, false)));
+ assertTrue(queue.needsKeyframe());
+ assertFalse(queue.offer(frame(5, false, false)));
+ assertSame(keyframe, queue.poll());
+ assertSame(delta, queue.poll());
+ }
+
+ @Test
+ public void newKeyframeReplacesOldMediaButKeepsConfig() {
+ VideoQueue queue = new VideoQueue(4, 4);
+ VideoQueue.Frame config = frame(1, true, false);
+ VideoQueue.Frame nextKeyframe = frame(4, false, true);
+ assertTrue(queue.offer(config));
+ assertTrue(queue.offer(frame(2, false, true)));
+ assertTrue(queue.offer(frame(3, false, false)));
+
+ assertTrue(queue.offer(nextKeyframe));
+
+ assertSame(config, queue.poll());
+ assertSame(nextKeyframe, queue.poll());
+ assertTrue(queue.isEmpty());
+ }
+}
diff --git a/app/src/test/java/invalid/lena/scrcpy/VideoStreamTest.java b/app/src/test/java/invalid/lena/scrcpy/VideoStreamTest.java
index 9d132f6..38d7416 100644
--- a/app/src/test/java/invalid/lena/scrcpy/VideoStreamTest.java
+++ b/app/src/test/java/invalid/lena/scrcpy/VideoStreamTest.java
@@ -19,8 +19,8 @@ public class VideoStreamTest {
private static final class RecordingFrames implements VideoFrames {
static final class Cfg { final int fourcc, w, h; Cfg(int f,int w,int h){this.fourcc=f;this.w=w;this.h=h;} }
- static final class Feed { final byte[] data; final long ptsUs; final boolean isCfg;
- Feed(byte[] d,long p,boolean c){data=d;ptsUs=p;isCfg=c;} }
+ static final class Feed { final byte[] data; final long ptsUs; final boolean isCfg, isKey;
+ Feed(byte[] d,long p,boolean c,boolean k){data=d;ptsUs=p;isCfg=c;isKey=k;} }
final List<Cfg> configs = new ArrayList<>();
final List<Cfg> reconfigs = new ArrayList<>();
@@ -29,7 +29,7 @@ public class VideoStreamTest {
@Override public void configure(int f, int w, int h) { configs.add(new Cfg(f, w, h)); }
@Override public void reconfigure(int f, int w, int h) { reconfigs.add(new Cfg(f, w, h)); }
- @Override public void feed(byte[] d, long pts, boolean c){ feeds.add(new Feed(d, pts, c)); }
+ @Override public void feed(byte[] d, long pts, boolean c, boolean k){ feeds.add(new Feed(d, pts, c, k)); }
@Override public void release() { releases++; }
}
@@ -106,6 +106,7 @@ public class VideoStreamTest {
// keyframe: keyframe flag is stripped from ptsUs
assertEquals(1_000_000L, sink.feeds.get(1).ptsUs);
assertEquals(5, sink.feeds.get(1).data.length);
+ assertTrue("second is keyframe", sink.feeds.get(1).isKey);
assertEquals(2_000_000L, sink.feeds.get(2).ptsUs);
}
@@ -171,60 +172,6 @@ public class VideoStreamTest {
}
@Test
- public void recorderTapSeesMetaAndFramesWithFlags() throws Exception {
- byte[] csd = new byte[]{0x67, 0x42, (byte)0xe0, 0x1e};
- byte[] keyframe = new byte[]{10, 11, 12};
- byte[] delta = new byte[]{20, 21};
- long PTS_CFG = 1L << 62;
- long PTS_KEY = (1L << 61) | 1_000_000L;
- long PTS_DELT = 2_000_000L;
-
- byte[] bytes = cat(
- fourcc(Wire.CODEC_H264),
- sessionMeta(800, 600, false),
- frame(PTS_CFG, csd),
- frame(PTS_KEY, keyframe),
- frame(PTS_DELT, delta));
-
- RecordingFrames sink = new RecordingFrames();
- RecordingRecorder rec = new RecordingRecorder();
- VideoStream s = new VideoStream(new ByteArrayInputStream(bytes), sink, null);
- s.setRecorder(rec);
- s.run();
-
- assertEquals(1, rec.metas.size());
- assertEquals(800, rec.metas.get(0)[1]);
- assertEquals(600, rec.metas.get(0)[2]);
-
- assertEquals(3, rec.frames.size());
- assertTrue("cfg frame", rec.frames.get(0).isConfig);
- assertTrue("key frame", rec.frames.get(1).isKeyframe);
- assertTrue("delta is not config or key",
- !rec.frames.get(2).isConfig && !rec.frames.get(2).isKeyframe);
- assertEquals(csd.length, rec.frames.get(0).data.length);
- assertEquals(keyframe.length, rec.frames.get(1).data.length);
- assertEquals(delta.length, rec.frames.get(2).data.length);
- }
-
- private static final class RecordingRecorder implements VideoRecorder {
- static final class FrameRec {
- final byte[] data; final long ptsUs;
- final boolean isConfig, isKeyframe;
- FrameRec(byte[] d, long p, boolean c, boolean k) {
- data = d; ptsUs = p; isConfig = c; isKeyframe = k;
- }
- }
- final List<int[]> metas = new ArrayList<>();
- final List<FrameRec> frames = new ArrayList<>();
- int closes = 0;
- @Override public void onMeta(int f, int w, int h) { metas.add(new int[]{f, w, h}); }
- @Override public void onFrame(byte[] d, long p, boolean c, boolean k) {
- frames.add(new FrameRec(d, p, c, k));
- }
- @Override public void close() { closes++; }
- }
-
- @Test
public void payloadIntegrity() throws Exception {
byte[] payload = new byte[1024];
for (int i = 0; i < payload.length; i++) payload[i] = (byte) (i & 0xff);
diff --git a/app/src/test/java/invalid/lena/scrcpy/WireTest.java b/app/src/test/java/invalid/lena/scrcpy/WireTest.java
index 9cee6c5..e4ada33 100644
--- a/app/src/test/java/invalid/lena/scrcpy/WireTest.java
+++ b/app/src/test/java/invalid/lena/scrcpy/WireTest.java
@@ -2,16 +2,33 @@ package invalid.lena.scrcpy;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
public class WireTest {
@Test
+ public void readFullyRejectsZeroProgress() throws Exception {
+ InputStream in = new InputStream() {
+ @Override public int read() { return 0; }
+ @Override public int read(byte[] b, int off, int len) { return 0; }
+ };
+ try {
+ Wire.readFully(in, new byte[1]);
+ fail("zero-progress input accepted");
+ } catch (IOException expected) {
+ assertTrue(expected.getMessage().contains("no progress"));
+ }
+ }
+
+ @Test
public void be32RoundTrip() {
byte[] b = new byte[4];
int[] cases = {0, 1, -1, 0x7fffffff, 0x80000000, 0x12345678};
@@ -67,28 +84,39 @@ public class WireTest {
}
}
+ // Derive the id the way scrcpy's Codec enums do: the ASCII name, right
+ // aligned in a big-endian uint32, NUL-padded on the left. Deriving it
+ // from the name is the whole point - the previous version of this test
+ // asserted the constants against hand-copied literals, so it passed
+ // while CODEC_AV1 held 'av01' instead of '\0av1' and AV1 could never
+ // negotiate.
+ private static int idOf(String name) {
+ if (name.length() > 4) throw new IllegalArgumentException(name);
+ int v = 0;
+ for (int i = 0; i < 4 - name.length(); i++) v <<= 8;
+ for (int i = 0; i < name.length(); i++) v = (v << 8) | name.charAt(i);
+ return v;
+ }
+
@Test
public void fourccConstantsMatchScrcpy() {
- // Big-endian uint32 of the 4-char ASCII tag, NUL-padded on the LEFT
- // for short names (raw, aac) - mirrors AudioCodec / VideoCodec.
- assertEquals(0x68_32_36_34, Wire.CODEC_H264); // "h264"
- assertEquals(0x68_32_36_35, Wire.CODEC_H265); // "h265"
- assertEquals(0x61_76_30_31, Wire.CODEC_AV1); // "av01"
- assertEquals(0x6f_70_75_73, Wire.CODEC_OPUS); // "opus"
- assertEquals(0x66_6c_61_63, Wire.CODEC_FLAC); // "flac"
- assertEquals(0x00_61_61_63, Wire.CODEC_AAC); // "\0aac"
- assertEquals(0x00_72_61_77, Wire.CODEC_RAW); // "\0raw"
+ // Names as spelled in scrcpy's VideoCodec / AudioCodec enums.
+ assertEquals(idOf("h264"), Wire.CODEC_H264);
+ assertEquals(idOf("h265"), Wire.CODEC_H265);
+ assertEquals(idOf("av1"), Wire.CODEC_AV1);
+ assertEquals(idOf("opus"), Wire.CODEC_OPUS);
+ assertEquals(idOf("raw"), Wire.CODEC_RAW);
}
@Test
- public void fourccName() {
+ public void fourccNameRoundTripsEveryCodec() {
assertEquals("h264", Wire.fourccName(Wire.CODEC_H264));
+ assertEquals("h265", Wire.fourccName(Wire.CODEC_H265));
+ assertEquals("av1", Wire.fourccName(Wire.CODEC_AV1));
assertEquals("opus", Wire.fourccName(Wire.CODEC_OPUS));
+ assertEquals("raw", Wire.fourccName(Wire.CODEC_RAW));
+ // Left-NUL padding is the rule, not a special case for raw.
+ assertEquals("aac", Wire.fourccName(0x00_61_61_63));
}
- @Test
- public void fourccNameTrimsLeadingNuls() {
- assertEquals("raw", Wire.fourccName(Wire.CODEC_RAW));
- assertEquals("aac", Wire.fourccName(Wire.CODEC_AAC));
- }
}