Skip to content

Commit 7e23fec

Browse files
committed
address code review comments
1 parent 6374c82 commit 7e23fec

11 files changed

Lines changed: 1268 additions & 175 deletions

File tree

‎app/src/androidTest/java/com/maxistar/textpad/test/EditorExternalChangeTest.java‎

Lines changed: 508 additions & 0 deletions
Large diffs are not rendered by default.

‎app/src/androidTest/java/com/maxistar/textpad/test/EditorRecoveryTest.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,22 @@ public void dismissingRecoveryKeepsDraft() throws Exception {
140140
}
141141
}
142142

143+
@Test
144+
public void pendingRecoveryDecisionCannotOverwriteDraftDuringRecreation() throws Exception {
145+
String content = "recoverable after recreation";
146+
try (ActivityScenario<EditorActivity> scenario = ActivityScenario.launch(EditorActivity.class)) {
147+
scenario.onActivity(activity -> ((EditText) activity.findViewById(R.id.editText1)).setText(content));
148+
scenario.recreate();
149+
onView(withText(R.string.Restore)).check(matches(androidx.test.espresso.matcher.ViewMatchers.isDisplayed()));
150+
pressBack();
151+
scenario.moveToState(Lifecycle.State.CREATED);
152+
scenario.moveToState(Lifecycle.State.RESUMED);
153+
assertEquals(content, new RecoveryRepository(context).loadActive().text);
154+
onView(withText(R.string.Restore)).perform(click());
155+
onView(withId(R.id.editText1)).check(matches(withText(content)));
156+
}
157+
}
158+
143159
@Test
144160
public void namedDraftIsDetectedByExactDocumentUri() throws Exception {
145161
String documentUri = "content://recovery-test/document/notes.txt";

‎app/src/androidTest/java/com/maxistar/textpad/test/recovery/RecoveryRepositoryTest.java‎

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,16 @@ public void writesSeparateDraftAndMetadataAndLoadsThem() throws Exception {
7676
assertNotNull(loaded);
7777
assertEquals("recover me", loaded.text);
7878
assertEquals(3, loaded.metadata.generation);
79-
assertTrue(new File(repository.getDirectoryForTests(), key + ".draft").isFile());
80-
assertTrue(new File(repository.getDirectoryForTests(), key + ".json").isFile());
79+
File generation = publishedGeneration(key);
80+
assertTrue(new File(generation, "draft.draft").isFile());
81+
assertTrue(new File(generation, "metadata.json").isFile());
8182
}
8283

8384
@Test
8485
public void invalidLengthAndMissingPairAreRejected() throws Exception {
8586
String key = RecoveryKeys.forUntitledDocument();
8687
repository.write(metadata(key, null, 1), "valid");
87-
File draft = new File(repository.getDirectoryForTests(), key + ".draft");
88+
File draft = new File(publishedGeneration(key), "draft.draft");
8889
try (FileOutputStream output = new FileOutputStream(draft, true)) {
8990
output.write('x');
9091
}
@@ -98,7 +99,7 @@ public void invalidLengthAndMissingPairAreRejected() throws Exception {
9899
public void sameLengthContentMismatchIsRejected() throws Exception {
99100
String key = RecoveryKeys.forUntitledDocument();
100101
repository.write(metadata(key, null, 1), "first");
101-
File draft = new File(repository.getDirectoryForTests(), key + ".draft");
102+
File draft = new File(publishedGeneration(key), "draft.draft");
102103
try (FileOutputStream output = new FileOutputStream(draft, false)) {
103104
output.write("other".getBytes(StandardCharsets.UTF_8));
104105
}
@@ -110,7 +111,7 @@ public void sameLengthContentMismatchIsRejected() throws Exception {
110111
public void staleActivePointerIsCleared() throws Exception {
111112
String key = RecoveryKeys.forUntitledDocument();
112113
repository.write(metadata(key, null, 1), "draft");
113-
new File(repository.getDirectoryForTests(), key + ".draft").delete();
114+
deleteRecursively(publishedGeneration(key));
114115

115116
assertNull(repository.loadActive());
116117
assertNull(ApplicationProvider.<Context>getApplicationContext()
@@ -140,8 +141,49 @@ public void deleteRemovesPairAndActivePointer() throws Exception {
140141
repository.delete(key);
141142

142143
assertNull(repository.load(key, null));
143-
assertFalse(new File(repository.getDirectoryForTests(), key + ".draft").exists());
144-
assertFalse(new File(repository.getDirectoryForTests(), key + ".json").exists());
144+
assertFalse(new File(repository.getDirectoryForTests(), key + ".current").exists());
145+
assertFalse(new File(repository.getDirectoryForTests(), key + ".generations").exists());
146+
}
147+
148+
@Test
149+
public void incompleteUnpublishedGenerationDoesNotReplaceCurrentDraft() throws Exception {
150+
String key = RecoveryKeys.forUntitledDocument();
151+
repository.write(metadata(key, null, 1), "previous");
152+
File generations = new File(repository.getDirectoryForTests(), key + ".generations");
153+
File incomplete = new File(generations, "generation-interrupted.tmp");
154+
assertTrue(incomplete.mkdir());
155+
try (FileOutputStream output = new FileOutputStream(new File(incomplete, "draft.draft"))) {
156+
output.write("newer".getBytes(StandardCharsets.UTF_8));
157+
}
158+
159+
repository.cleanupIncompleteArtifacts();
160+
161+
RecoveryDraft draft = repository.loadActive();
162+
assertNotNull(draft);
163+
assertEquals("previous", draft.text);
164+
assertFalse(incomplete.exists());
165+
}
166+
167+
@Test
168+
public void cleanupRestoresLegacyBackupBeforeLoading() throws Exception {
169+
String key = RecoveryKeys.forUntitledDocument();
170+
RecoveryMetadata published = metadata(key, null, 1).published(
171+
"valid".getBytes(StandardCharsets.UTF_8).length,
172+
sha256("valid".getBytes(StandardCharsets.UTF_8)),
173+
123L
174+
);
175+
File directory = repository.getDirectoryForTests();
176+
assertTrue(directory.mkdirs());
177+
write(new File(directory, key + ".draft"), "broken");
178+
write(new File(directory, key + ".draft.bak"), "valid");
179+
write(new File(directory, key + ".json"), published.toJson().toString());
180+
181+
repository.cleanupIncompleteArtifacts();
182+
183+
RecoveryDraft draft = repository.load(key, null);
184+
assertNotNull(draft);
185+
assertEquals("valid", draft.text);
186+
assertFalse(new File(directory, key + ".draft.bak").exists());
145187
}
146188

147189
private RecoveryMetadata metadata(String key, String uri, long generation) {
@@ -151,6 +193,29 @@ private RecoveryMetadata metadata(String key, String uri, long generation) {
151193
);
152194
}
153195

196+
private File publishedGeneration(String key) {
197+
File generations = new File(repository.getDirectoryForTests(), key + ".generations");
198+
File[] children = generations.listFiles(file -> file.isDirectory() && !file.getName().endsWith(".tmp"));
199+
assertNotNull(children);
200+
assertEquals(1, children.length);
201+
return children[0];
202+
}
203+
204+
private static void write(File file, String value) throws Exception {
205+
try (FileOutputStream output = new FileOutputStream(file)) {
206+
output.write(value.getBytes(StandardCharsets.UTF_8));
207+
}
208+
}
209+
210+
private static String sha256(byte[] value) throws Exception {
211+
java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256");
212+
StringBuilder result = new StringBuilder();
213+
for (byte item : digest.digest(value)) {
214+
result.append(String.format(java.util.Locale.ROOT, "%02x", item & 0xff));
215+
}
216+
return result.toString();
217+
}
218+
154219
private static void deleteRecursively(File file) {
155220
if (!file.exists()) {
156221
return;

‎app/src/androidTest/java/com/maxistar/textpad/test/recovery/RecoveryWriterTest.java‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public synchronized RecoveryMetadata write(RecoveryMetadata metadata, String tex
9393
public void cancellationBarrierPreventsQueuedDraftFromReappearingAfterDelete() {
9494
String key = RecoveryKeys.forUntitledDocument();
9595
writer.schedule(snapshot(key, 3, "queued"));
96-
writer.cancelAndWait(3, 2000);
96+
writer.cancelAndWait(key, 3, 2000);
9797
repository.delete(key);
9898

9999
try {
@@ -104,6 +104,19 @@ public void cancellationBarrierPreventsQueuedDraftFromReappearingAfterDelete() {
104104
assertEquals(null, repository.load(key, null));
105105
}
106106

107+
@Test
108+
public void lowerGenerationForAnotherKeyIsNotRejected() {
109+
String firstKey = RecoveryKeys.forUntitledDocument();
110+
String secondKey = RecoveryKeys.forUntitledDocument();
111+
writer.flushAndWait(snapshot(firstKey, 9, "first"), 2000);
112+
writer.flushAndWait(snapshot(secondKey, 1, "second"), 2000);
113+
114+
RecoveryDraft draft = repository.load(secondKey, null);
115+
assertNotNull(draft);
116+
assertEquals("second", draft.text);
117+
assertEquals(1, draft.metadata.generation);
118+
}
119+
107120
private RecoveryWriter.Snapshot snapshot(String key, long generation, String text) {
108121
RecoveryMetadata metadata = new RecoveryMetadata(
109122
key, null, "newfile.txt", true, "UTF-8", false,

0 commit comments

Comments
 (0)