Rename DownloadThread to Task
This resolves some naming confusion that previously existed as a result of DownloadThread also being used for removals. Some related variables (e.g. activeDownloadCount) would refer to both download and removal tasks, whilst others (e.g. maxParallelDownloads) would refer only to downloads. This change renames those that refer to both to use "task" terminology. This change also includes minor test edits. PiperOrigin-RevId: 245913671
This commit is contained in:
parent
44ad47746a
commit
51914f8f82
@ -137,7 +137,7 @@ public final class DownloadManager {
|
|||||||
private static final int MSG_SET_MIN_RETRY_COUNT = 5;
|
private static final int MSG_SET_MIN_RETRY_COUNT = 5;
|
||||||
private static final int MSG_ADD_DOWNLOAD = 6;
|
private static final int MSG_ADD_DOWNLOAD = 6;
|
||||||
private static final int MSG_REMOVE_DOWNLOAD = 7;
|
private static final int MSG_REMOVE_DOWNLOAD = 7;
|
||||||
private static final int MSG_DOWNLOAD_THREAD_STOPPED = 8;
|
private static final int MSG_TASK_STOPPED = 8;
|
||||||
private static final int MSG_CONTENT_LENGTH_CHANGED = 9;
|
private static final int MSG_CONTENT_LENGTH_CHANGED = 9;
|
||||||
private static final int MSG_RELEASE = 10;
|
private static final int MSG_RELEASE = 10;
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ public final class DownloadManager {
|
|||||||
private final ArrayList<Download> downloads;
|
private final ArrayList<Download> downloads;
|
||||||
|
|
||||||
private int pendingMessages;
|
private int pendingMessages;
|
||||||
private int activeDownloadCount;
|
private int activeTaskCount;
|
||||||
private boolean initialized;
|
private boolean initialized;
|
||||||
private boolean downloadsPaused;
|
private boolean downloadsPaused;
|
||||||
private int maxParallelDownloads;
|
private int maxParallelDownloads;
|
||||||
@ -244,7 +244,7 @@ public final class DownloadManager {
|
|||||||
* download requirements are not met).
|
* download requirements are not met).
|
||||||
*/
|
*/
|
||||||
public boolean isIdle() {
|
public boolean isIdle() {
|
||||||
return activeDownloadCount == 0 && pendingMessages == 0;
|
return activeTaskCount == 0 && pendingMessages == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -465,7 +465,7 @@ public final class DownloadManager {
|
|||||||
mainHandler.removeCallbacksAndMessages(/* token= */ null);
|
mainHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||||
// Reset state.
|
// Reset state.
|
||||||
pendingMessages = 0;
|
pendingMessages = 0;
|
||||||
activeDownloadCount = 0;
|
activeTaskCount = 0;
|
||||||
initialized = false;
|
initialized = false;
|
||||||
downloads.clear();
|
downloads.clear();
|
||||||
}
|
}
|
||||||
@ -503,8 +503,8 @@ public final class DownloadManager {
|
|||||||
break;
|
break;
|
||||||
case MSG_PROCESSED:
|
case MSG_PROCESSED:
|
||||||
int processedMessageCount = message.arg1;
|
int processedMessageCount = message.arg1;
|
||||||
int activeDownloadCount = message.arg2;
|
int activeTaskCount = message.arg2;
|
||||||
onMessageProcessed(processedMessageCount, activeDownloadCount);
|
onMessageProcessed(processedMessageCount, activeTaskCount);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
@ -543,9 +543,9 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMessageProcessed(int processedMessageCount, int activeDownloadCount) {
|
private void onMessageProcessed(int processedMessageCount, int activeTaskCount) {
|
||||||
this.pendingMessages -= processedMessageCount;
|
this.pendingMessages -= processedMessageCount;
|
||||||
this.activeDownloadCount = activeDownloadCount;
|
this.activeTaskCount = activeTaskCount;
|
||||||
if (isIdle()) {
|
if (isIdle()) {
|
||||||
for (Listener listener : listeners) {
|
for (Listener listener : listeners) {
|
||||||
listener.onIdle(this);
|
listener.onIdle(this);
|
||||||
@ -627,7 +627,7 @@ public final class DownloadManager {
|
|||||||
private final DownloaderFactory downloaderFactory;
|
private final DownloaderFactory downloaderFactory;
|
||||||
private final Handler mainHandler;
|
private final Handler mainHandler;
|
||||||
private final ArrayList<DownloadInternal> downloadInternals;
|
private final ArrayList<DownloadInternal> downloadInternals;
|
||||||
private final HashMap<String, DownloadThread> downloadThreads;
|
private final HashMap<String, Task> activeTasks;
|
||||||
|
|
||||||
// Mutable fields that are accessed on the internal thread.
|
// Mutable fields that are accessed on the internal thread.
|
||||||
@Requirements.RequirementFlags private int notMetRequirements;
|
@Requirements.RequirementFlags private int notMetRequirements;
|
||||||
@ -653,7 +653,7 @@ public final class DownloadManager {
|
|||||||
this.minRetryCount = minRetryCount;
|
this.minRetryCount = minRetryCount;
|
||||||
this.downloadsPaused = downloadsPaused;
|
this.downloadsPaused = downloadsPaused;
|
||||||
downloadInternals = new ArrayList<>();
|
downloadInternals = new ArrayList<>();
|
||||||
downloadThreads = new HashMap<>();
|
activeTasks = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -694,14 +694,14 @@ public final class DownloadManager {
|
|||||||
id = (String) message.obj;
|
id = (String) message.obj;
|
||||||
removeDownload(id);
|
removeDownload(id);
|
||||||
break;
|
break;
|
||||||
case MSG_DOWNLOAD_THREAD_STOPPED:
|
case MSG_TASK_STOPPED:
|
||||||
DownloadThread downloadThread = (DownloadThread) message.obj;
|
Task task = (Task) message.obj;
|
||||||
onDownloadThreadStopped(downloadThread);
|
onTaskStopped(task);
|
||||||
processedExternalMessage = false; // This message is posted internally.
|
processedExternalMessage = false; // This message is posted internally.
|
||||||
break;
|
break;
|
||||||
case MSG_CONTENT_LENGTH_CHANGED:
|
case MSG_CONTENT_LENGTH_CHANGED:
|
||||||
downloadThread = (DownloadThread) message.obj;
|
task = (Task) message.obj;
|
||||||
onDownloadThreadContentLengthChanged(downloadThread);
|
onContentLengthChanged(task);
|
||||||
processedExternalMessage = false; // This message is posted internally.
|
processedExternalMessage = false; // This message is posted internally.
|
||||||
break;
|
break;
|
||||||
case MSG_RELEASE:
|
case MSG_RELEASE:
|
||||||
@ -711,7 +711,7 @@ public final class DownloadManager {
|
|||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
mainHandler
|
mainHandler
|
||||||
.obtainMessage(MSG_PROCESSED, processedExternalMessage ? 1 : 0, downloadThreads.size())
|
.obtainMessage(MSG_PROCESSED, processedExternalMessage ? 1 : 0, activeTasks.size())
|
||||||
.sendToTarget();
|
.sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,18 +832,17 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDownloadThreadStopped(DownloadThread downloadThread) {
|
private void onTaskStopped(Task task) {
|
||||||
logd("Download is stopped", downloadThread.request);
|
logd("Task is stopped", task.request);
|
||||||
String downloadId = downloadThread.request.id;
|
String downloadId = task.request.id;
|
||||||
downloadThreads.remove(downloadId);
|
activeTasks.remove(downloadId);
|
||||||
boolean tryToStartDownloads = false;
|
boolean tryToStartDownloads = false;
|
||||||
if (!downloadThread.isRemove) {
|
if (!task.isRemove) {
|
||||||
// If maxParallelDownloads was hit, there might be a download waiting for a slot.
|
// If maxParallelDownloads was hit, there might be a download waiting for a slot.
|
||||||
tryToStartDownloads = parallelDownloads == maxParallelDownloads;
|
tryToStartDownloads = parallelDownloads == maxParallelDownloads;
|
||||||
parallelDownloads--;
|
parallelDownloads--;
|
||||||
}
|
}
|
||||||
getDownload(downloadId)
|
getDownload(downloadId).onTaskStopped(task.isCanceled, task.finalError);
|
||||||
.onDownloadThreadStopped(downloadThread.isCanceled, downloadThread.finalError);
|
|
||||||
if (tryToStartDownloads) {
|
if (tryToStartDownloads) {
|
||||||
for (int i = 0;
|
for (int i = 0;
|
||||||
parallelDownloads < maxParallelDownloads && i < downloadInternals.size();
|
parallelDownloads < maxParallelDownloads && i < downloadInternals.size();
|
||||||
@ -853,16 +852,16 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDownloadThreadContentLengthChanged(DownloadThread downloadThread) {
|
private void onContentLengthChanged(Task task) {
|
||||||
String downloadId = downloadThread.request.id;
|
String downloadId = task.request.id;
|
||||||
getDownload(downloadId).setContentLength(downloadThread.contentLength);
|
getDownload(downloadId).setContentLength(task.contentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void release() {
|
private void release() {
|
||||||
for (DownloadThread downloadThread : downloadThreads.values()) {
|
for (Task task : activeTasks.values()) {
|
||||||
downloadThread.cancel(/* released= */ true);
|
task.cancel(/* released= */ true);
|
||||||
}
|
}
|
||||||
downloadThreads.clear();
|
activeTasks.clear();
|
||||||
downloadInternals.clear();
|
downloadInternals.clear();
|
||||||
thread.quit();
|
thread.quit();
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
@ -871,7 +870,7 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDownloadChangedInternal(DownloadInternal downloadInternal, Download download) {
|
private void onDownloadChanged(DownloadInternal downloadInternal, Download download) {
|
||||||
logd("Download state is changed", downloadInternal);
|
logd("Download state is changed", downloadInternal);
|
||||||
try {
|
try {
|
||||||
downloadIndex.putDownload(download);
|
downloadIndex.putDownload(download);
|
||||||
@ -884,7 +883,7 @@ public final class DownloadManager {
|
|||||||
mainHandler.obtainMessage(MSG_DOWNLOAD_CHANGED, download).sendToTarget();
|
mainHandler.obtainMessage(MSG_DOWNLOAD_CHANGED, download).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDownloadRemovedInternal(DownloadInternal downloadInternal, Download download) {
|
private void onDownloadRemoved(DownloadInternal downloadInternal, Download download) {
|
||||||
logd("Download is removed", downloadInternal);
|
logd("Download is removed", downloadInternal);
|
||||||
try {
|
try {
|
||||||
downloadIndex.removeDownload(download.request.id);
|
downloadIndex.removeDownload(download.request.id);
|
||||||
@ -896,11 +895,11 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@StartThreadResults
|
@StartThreadResults
|
||||||
private int startDownloadThread(DownloadInternal downloadInternal) {
|
private int startTask(DownloadInternal downloadInternal) {
|
||||||
DownloadRequest request = downloadInternal.download.request;
|
DownloadRequest request = downloadInternal.download.request;
|
||||||
String downloadId = request.id;
|
String downloadId = request.id;
|
||||||
if (downloadThreads.containsKey(downloadId)) {
|
if (activeTasks.containsKey(downloadId)) {
|
||||||
if (stopDownloadThreadInternal(downloadId)) {
|
if (stopDownloadTask(downloadId)) {
|
||||||
return START_THREAD_WAIT_DOWNLOAD_CANCELLATION;
|
return START_THREAD_WAIT_DOWNLOAD_CANCELLATION;
|
||||||
}
|
}
|
||||||
return START_THREAD_WAIT_REMOVAL_TO_FINISH;
|
return START_THREAD_WAIT_REMOVAL_TO_FINISH;
|
||||||
@ -914,19 +913,25 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
Downloader downloader = downloaderFactory.createDownloader(request);
|
Downloader downloader = downloaderFactory.createDownloader(request);
|
||||||
DownloadProgress downloadProgress = downloadInternal.download.progress;
|
DownloadProgress downloadProgress = downloadInternal.download.progress;
|
||||||
DownloadThread downloadThread =
|
Task task =
|
||||||
new DownloadThread(request, downloader, downloadProgress, isRemove, minRetryCount, this);
|
new Task(
|
||||||
downloadThreads.put(downloadId, downloadThread);
|
request,
|
||||||
downloadThread.start();
|
downloader,
|
||||||
logd("Download is started", downloadInternal);
|
downloadProgress,
|
||||||
|
isRemove,
|
||||||
|
minRetryCount,
|
||||||
|
/* internalHandler= */ this);
|
||||||
|
activeTasks.put(downloadId, task);
|
||||||
|
task.start();
|
||||||
|
logd("Task is started", downloadInternal);
|
||||||
return START_THREAD_SUCCEEDED;
|
return START_THREAD_SUCCEEDED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean stopDownloadThreadInternal(String downloadId) {
|
private boolean stopDownloadTask(String downloadId) {
|
||||||
DownloadThread downloadThread = downloadThreads.get(downloadId);
|
Task task = activeTasks.get(downloadId);
|
||||||
if (downloadThread != null && !downloadThread.isRemove) {
|
if (task != null && !task.isRemove) {
|
||||||
downloadThread.cancel(/* released= */ false);
|
task.cancel(/* released= */ false);
|
||||||
logd("Download is cancelled", downloadThread.request);
|
logd("Task is cancelled", task.request);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -1025,7 +1030,7 @@ public final class DownloadManager {
|
|||||||
if (state == STATE_QUEUED || state == STATE_DOWNLOADING) {
|
if (state == STATE_QUEUED || state == STATE_DOWNLOADING) {
|
||||||
startOrQueue();
|
startOrQueue();
|
||||||
} else if (isInRemoveState()) {
|
} else if (isInRemoveState()) {
|
||||||
internalHandler.startDownloadThread(this);
|
internalHandler.startTask(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1043,7 +1048,7 @@ public final class DownloadManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.contentLength = contentLength;
|
this.contentLength = contentLength;
|
||||||
internalHandler.onDownloadChangedInternal(this, getUpdatedDownload());
|
internalHandler.onDownloadChanged(this, getUpdatedDownload());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStopState() {
|
private void updateStopState() {
|
||||||
@ -1054,12 +1059,12 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (state == STATE_DOWNLOADING || state == STATE_QUEUED) {
|
if (state == STATE_DOWNLOADING || state == STATE_QUEUED) {
|
||||||
internalHandler.stopDownloadThreadInternal(download.request.id);
|
internalHandler.stopDownloadTask(download.request.id);
|
||||||
setState(STATE_STOPPED);
|
setState(STATE_STOPPED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldDownload == download) {
|
if (oldDownload == download) {
|
||||||
internalHandler.onDownloadChangedInternal(this, getUpdatedDownload());
|
internalHandler.onDownloadChanged(this, getUpdatedDownload());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1068,14 +1073,14 @@ public final class DownloadManager {
|
|||||||
// state immediately.
|
// state immediately.
|
||||||
state = initialState;
|
state = initialState;
|
||||||
if (isInRemoveState()) {
|
if (isInRemoveState()) {
|
||||||
internalHandler.startDownloadThread(this);
|
internalHandler.startTask(this);
|
||||||
} else if (canStart()) {
|
} else if (canStart()) {
|
||||||
startOrQueue();
|
startOrQueue();
|
||||||
} else {
|
} else {
|
||||||
setState(STATE_STOPPED);
|
setState(STATE_STOPPED);
|
||||||
}
|
}
|
||||||
if (state == initialState) {
|
if (state == initialState) {
|
||||||
internalHandler.onDownloadChangedInternal(this, getUpdatedDownload());
|
internalHandler.onDownloadChanged(this, getUpdatedDownload());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1085,7 +1090,7 @@ public final class DownloadManager {
|
|||||||
|
|
||||||
private void startOrQueue() {
|
private void startOrQueue() {
|
||||||
Assertions.checkState(!isInRemoveState());
|
Assertions.checkState(!isInRemoveState());
|
||||||
@StartThreadResults int result = internalHandler.startDownloadThread(this);
|
@StartThreadResults int result = internalHandler.startTask(this);
|
||||||
Assertions.checkState(result != START_THREAD_WAIT_REMOVAL_TO_FINISH);
|
Assertions.checkState(result != START_THREAD_WAIT_REMOVAL_TO_FINISH);
|
||||||
if (result == START_THREAD_SUCCEEDED || result == START_THREAD_WAIT_DOWNLOAD_CANCELLATION) {
|
if (result == START_THREAD_SUCCEEDED || result == START_THREAD_WAIT_DOWNLOAD_CANCELLATION) {
|
||||||
setState(STATE_DOWNLOADING);
|
setState(STATE_DOWNLOADING);
|
||||||
@ -1097,18 +1102,18 @@ public final class DownloadManager {
|
|||||||
private void setState(@Download.State int newState) {
|
private void setState(@Download.State int newState) {
|
||||||
if (state != newState) {
|
if (state != newState) {
|
||||||
state = newState;
|
state = newState;
|
||||||
internalHandler.onDownloadChangedInternal(this, getUpdatedDownload());
|
internalHandler.onDownloadChanged(this, getUpdatedDownload());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDownloadThreadStopped(boolean isCanceled, @Nullable Throwable error) {
|
private void onTaskStopped(boolean isCanceled, @Nullable Throwable error) {
|
||||||
if (isIdle()) {
|
if (isIdle()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isCanceled) {
|
if (isCanceled) {
|
||||||
internalHandler.startDownloadThread(this);
|
internalHandler.startTask(this);
|
||||||
} else if (state == STATE_REMOVING) {
|
} else if (state == STATE_REMOVING) {
|
||||||
internalHandler.onDownloadRemovedInternal(this, getUpdatedDownload());
|
internalHandler.onDownloadRemoved(this, getUpdatedDownload());
|
||||||
} else if (state == STATE_RESTARTING) {
|
} else if (state == STATE_RESTARTING) {
|
||||||
initialize(STATE_QUEUED);
|
initialize(STATE_QUEUED);
|
||||||
} else { // STATE_DOWNLOADING
|
} else { // STATE_DOWNLOADING
|
||||||
@ -1123,7 +1128,7 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DownloadThread extends Thread implements Downloader.ProgressListener {
|
private static class Task extends Thread implements Downloader.ProgressListener {
|
||||||
|
|
||||||
private final DownloadRequest request;
|
private final DownloadRequest request;
|
||||||
private final Downloader downloader;
|
private final Downloader downloader;
|
||||||
@ -1137,7 +1142,7 @@ public final class DownloadManager {
|
|||||||
|
|
||||||
private long contentLength;
|
private long contentLength;
|
||||||
|
|
||||||
private DownloadThread(
|
private Task(
|
||||||
DownloadRequest request,
|
DownloadRequest request,
|
||||||
Downloader downloader,
|
Downloader downloader,
|
||||||
DownloadProgress downloadProgress,
|
DownloadProgress downloadProgress,
|
||||||
@ -1203,7 +1208,7 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
Handler internalHandler = this.internalHandler;
|
Handler internalHandler = this.internalHandler;
|
||||||
if (internalHandler != null) {
|
if (internalHandler != null) {
|
||||||
internalHandler.obtainMessage(MSG_DOWNLOAD_THREAD_STOPPED, this).sendToTarget();
|
internalHandler.obtainMessage(MSG_TASK_STOPPED, this).sendToTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ import java.util.List;
|
|||||||
@Nullable private String cacheKey;
|
@Nullable private String cacheKey;
|
||||||
private byte[] customMetadata;
|
private byte[] customMetadata;
|
||||||
|
|
||||||
private int state;
|
@Download.State private int state;
|
||||||
private long startTimeMs;
|
private long startTimeMs;
|
||||||
private long updateTimeMs;
|
private long updateTimeMs;
|
||||||
private long contentLength;
|
private long contentLength;
|
||||||
@ -111,7 +111,7 @@ import java.util.List;
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadBuilder setState(int state) {
|
public DownloadBuilder setState(@Download.State int state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -359,7 +359,7 @@ public class DownloadManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stopAndResume() throws Throwable {
|
public void pauseAndResume() throws Throwable {
|
||||||
DownloadRunner runner1 = new DownloadRunner(uri1);
|
DownloadRunner runner1 = new DownloadRunner(uri1);
|
||||||
DownloadRunner runner2 = new DownloadRunner(uri2);
|
DownloadRunner runner2 = new DownloadRunner(uri2);
|
||||||
DownloadRunner runner3 = new DownloadRunner(uri3);
|
DownloadRunner runner3 = new DownloadRunner(uri3);
|
||||||
@ -370,10 +370,12 @@ public class DownloadManagerTest {
|
|||||||
|
|
||||||
runOnMainThread(() -> downloadManager.pauseDownloads());
|
runOnMainThread(() -> downloadManager.pauseDownloads());
|
||||||
|
|
||||||
|
// TODO: This should be assertQueued. Fix implementation and update test.
|
||||||
runner1.getTask().assertStopped();
|
runner1.getTask().assertStopped();
|
||||||
|
|
||||||
// remove requests aren't stopped.
|
// remove requests aren't stopped.
|
||||||
runner2.getDownloader(1).unblock().assertReleased();
|
runner2.getDownloader(1).unblock().assertReleased();
|
||||||
|
// TODO: This should be assertQueued. Fix implementation and update test.
|
||||||
runner2.getTask().assertStopped();
|
runner2.getTask().assertStopped();
|
||||||
// Although remove2 is finished, download2 doesn't start.
|
// Although remove2 is finished, download2 doesn't start.
|
||||||
runner2.getDownloader(2).assertDoesNotStart();
|
runner2.getDownloader(2).assertDoesNotStart();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user