| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed 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 com.android.tradefed.cluster; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.argThat; |
| import static org.mockito.Mockito.RETURNS_DEEP_STUBS; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import com.android.tradefed.build.IBuildInfo; |
| import com.android.tradefed.cluster.ClusterCommand.RequestType; |
| import com.android.tradefed.cluster.ClusterCommandScheduler.InvocationEventHandler; |
| import com.android.tradefed.command.CommandScheduler; |
| import com.android.tradefed.command.remote.DeviceDescriptor; |
| import com.android.tradefed.config.Configuration; |
| import com.android.tradefed.config.ConfigurationException; |
| import com.android.tradefed.config.ConfigurationFactory; |
| import com.android.tradefed.config.GlobalConfiguration; |
| import com.android.tradefed.config.IConfiguration; |
| import com.android.tradefed.config.IDeviceConfiguration; |
| import com.android.tradefed.config.Option; |
| import com.android.tradefed.device.DeviceAllocationState; |
| import com.android.tradefed.device.FreeDeviceState; |
| import com.android.tradefed.device.IDeviceManager; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.device.MockDeviceManager; |
| import com.android.tradefed.device.NoDeviceException; |
| import com.android.tradefed.device.StubDevice; |
| import com.android.tradefed.host.IHostOptions; |
| import com.android.tradefed.host.IHostOptions.PermitLimitType; |
| import com.android.tradefed.invoker.IInvocationContext; |
| import com.android.tradefed.invoker.InvocationContext; |
| import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; |
| import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; |
| import com.android.tradefed.result.ConsoleResultReporter; |
| import com.android.tradefed.result.FileInputStreamSource; |
| import com.android.tradefed.result.LogDataType; |
| import com.android.tradefed.result.TestDescription; |
| import com.android.tradefed.result.TestSummary; |
| import com.android.tradefed.targetprep.ITargetPreparer; |
| import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; |
| import com.android.tradefed.util.ArrayUtil; |
| import com.android.tradefed.util.FileUtil; |
| import com.android.tradefed.util.IRestApiHelper; |
| import com.android.tradefed.util.MultiMap; |
| import com.android.tradefed.util.RunUtil; |
| import com.android.tradefed.util.ZipUtil; |
| import com.android.tradefed.util.keystore.IKeyStoreClient; |
| import com.android.tradefed.util.keystore.StubKeyStoreClient; |
| |
| import com.google.api.client.http.GenericUrl; |
| import com.google.api.client.http.HttpRequestFactory; |
| import com.google.api.client.http.HttpResponse; |
| import com.google.api.client.http.LowLevelHttpRequest; |
| import com.google.api.client.http.LowLevelHttpResponse; |
| import com.google.api.client.testing.http.MockHttpTransport; |
| import com.google.api.client.testing.http.MockLowLevelHttpRequest; |
| import com.google.api.client.testing.http.MockLowLevelHttpResponse; |
| import com.google.common.collect.ImmutableMap; |
| |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| import org.mockito.AdditionalMatchers; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.ArgumentMatcher; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.TreeMap; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| |
| /** Unit tests for {@link ClusterCommandScheduler}. */ |
| @RunWith(JUnit4.class) |
| public class ClusterCommandSchedulerTest { |
| |
| private static final String CLUSTER_ID = "free_pool"; |
| private static final String REQUEST_ID = "request_id"; |
| private static final String COMMAND_ID = "command_id"; |
| private static final String ATTEMPT_ID = "attempt_id"; |
| private static final String TASK_ID = "task_id"; |
| private static final String CMD_LINE = "test"; |
| private static final String DEVICE_SERIAL = "serial"; |
| private static final Set<String> deviceSerials = |
| new HashSet<String>(Arrays.asList(ClusterCommandSchedulerTest.DEVICE_SERIAL)); |
| |
| private static final boolean eventMatches( |
| ClusterCommandEvent event, ClusterCommandEvent.Type type) { |
| return TASK_ID.equals(event.getCommandTaskId()) |
| && ClusterCommandSchedulerTest.deviceSerials.equals(event.getDeviceSerials()) |
| && (event.getType() == type); |
| } |
| |
| private static final class IsInvocationInitiated |
| implements ArgumentMatcher<ClusterCommandEvent> { |
| @Override |
| public boolean matches(ClusterCommandEvent event) { |
| return eventMatches(event, ClusterCommandEvent.Type.InvocationInitiated); |
| } |
| } |
| |
| private static final class IsInvocationStarted implements ArgumentMatcher<ClusterCommandEvent> { |
| @Override |
| public boolean matches(ClusterCommandEvent event) { |
| return eventMatches(event, ClusterCommandEvent.Type.InvocationStarted); |
| } |
| } |
| |
| private static final class IsInvocationEnded implements ArgumentMatcher<ClusterCommandEvent> { |
| @Override |
| public boolean matches(ClusterCommandEvent event) { |
| return eventMatches(event, ClusterCommandEvent.Type.InvocationEnded); |
| } |
| } |
| |
| private static final class IsInvocationCompleted |
| implements ArgumentMatcher<ClusterCommandEvent> { |
| @Override |
| public boolean matches(ClusterCommandEvent event) { |
| return eventMatches(event, ClusterCommandEvent.Type.InvocationCompleted); |
| } |
| } |
| |
| @Rule public TestLogData mTestLog = new TestLogData(); |
| |
| @Mock IDeviceManager mMockDeviceManager; |
| @Mock IHostOptions mMockHostOptions; |
| @Mock IRestApiHelper mMockApiHelper; |
| private IClusterClient mMockClusterClient; |
| private ClusterOptions mMockClusterOptions; |
| |
| @SuppressWarnings("unchecked") |
| private IClusterEventUploader<ClusterCommandEvent> mMockEventUploader; |
| |
| private ClusterCommandScheduler mScheduler; |
| private IClusterEventUploader<ClusterHostEvent> mMockHostUploader; |
| // Test variable to store the args of last execCommand called by CommandScheduler. |
| Stack<ArrayList<String>> mExecCmdArgs = new Stack<>(); |
| |
| String[] getExecCommandArgs() { |
| ArrayList<String> execCmdArgs = mExecCmdArgs.pop(); |
| String[] args = new String[execCmdArgs.size()]; |
| return execCmdArgs.toArray(args); |
| } |
| |
| // Explicitly define this, so we can mock it |
| private static interface ICommandEventUploader |
| extends IClusterEventUploader<ClusterCommandEvent> {} |
| |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| |
| mMockHostUploader = mock(IClusterEventUploader.class); |
| mMockEventUploader = mock(ICommandEventUploader.class); |
| |
| mMockClusterOptions = new ClusterOptions(); |
| mMockClusterOptions.setCheckFlashingPermitsLease(false); |
| mMockClusterOptions.setClusterId(CLUSTER_ID); |
| mMockClusterClient = |
| new ClusterClient() { |
| @Override |
| public IClusterEventUploader<ClusterCommandEvent> getCommandEventUploader() { |
| return mMockEventUploader; |
| } |
| |
| @Override |
| public IClusterEventUploader<ClusterHostEvent> getHostEventUploader() { |
| return mMockHostUploader; |
| } |
| |
| @Override |
| public IClusterOptions getClusterOptions() { |
| return mMockClusterOptions; |
| } |
| |
| @Override |
| IRestApiHelper getApiHelper() { |
| return mMockApiHelper; |
| } |
| }; |
| |
| mScheduler = |
| new ClusterCommandScheduler() { |
| @Override |
| public IClusterOptions getClusterOptions() { |
| return mMockClusterOptions; |
| } |
| |
| @Override |
| IClusterClient getClusterClient() { |
| return mMockClusterClient; |
| } |
| |
| @Override |
| protected IDeviceManager getDeviceManager() { |
| return mMockDeviceManager; |
| } |
| |
| @Override |
| protected IHostOptions getHostOptions() { |
| return mMockHostOptions; |
| } |
| |
| @Override |
| public void execCommand(IScheduledInvocationListener listener, String[] args) |
| throws ConfigurationException, NoDeviceException { |
| ArrayList<String> execCmdArgs = new ArrayList<>(); |
| for (String arg : args) { |
| execCmdArgs.add(arg); |
| } |
| mExecCmdArgs.push(execCmdArgs); |
| } |
| |
| @Override |
| protected boolean dryRunCommand( |
| final InvocationEventHandler handler, String[] args) { |
| return false; |
| } |
| }; |
| } |
| |
| private static class FakeHttpTransport extends MockHttpTransport { |
| |
| private byte[] mResponseBytes; |
| |
| public FakeHttpTransport(byte[] responseBytes) { |
| mResponseBytes = responseBytes; |
| } |
| |
| @Override |
| public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { |
| return new MockLowLevelHttpRequest() { |
| @Override |
| public LowLevelHttpResponse execute() throws IOException { |
| MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); |
| response.setContent(new ByteArrayInputStream(mResponseBytes)); |
| return response; |
| } |
| }; |
| } |
| } |
| |
| private HttpResponse buildHttpResponse(String response) throws IOException { |
| HttpRequestFactory factory = |
| new FakeHttpTransport(response.getBytes()).createRequestFactory(); |
| // The method and url aren't used by our fake transport, but they can't be null |
| return factory.buildRequest("GET", new GenericUrl("http://example.com"), null).execute(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| mExecCmdArgs.clear(); |
| } |
| |
| private DeviceDescriptor createDevice( |
| String product, String variant, DeviceAllocationState state) { |
| return createDevice(DEVICE_SERIAL, product, variant, state); |
| } |
| |
| private DeviceDescriptor createDevice( |
| String serial, String product, String variant, DeviceAllocationState state) { |
| return new DeviceDescriptor( |
| serial, false, state, product, variant, "sdkVersion", "buildId", "batteryLevel"); |
| } |
| |
| @Test |
| public void testGetAvailableDevices() { |
| final List<DeviceDescriptor> deviceList = new ArrayList<>(); |
| deviceList.add(createDevice("product1", "variant1", DeviceAllocationState.Available)); |
| deviceList.add(createDevice("product2", "variant2", DeviceAllocationState.Available)); |
| deviceList.add(createDevice("product2", "variant2", DeviceAllocationState.Allocated)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Available)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Allocated)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Unavailable)); |
| when(mMockDeviceManager.listAllDevices()).thenReturn(deviceList); |
| |
| final MultiMap<String, DeviceDescriptor> deviceMap = |
| mScheduler.getDevices(mMockDeviceManager, false); |
| |
| assertTrue(deviceMap.containsKey("product1:variant1")); |
| assertEquals(1, deviceMap.get("product1:variant1").size()); |
| assertTrue(deviceMap.containsKey("product2:variant2")); |
| assertEquals(2, deviceMap.get("product2:variant2").size()); |
| assertTrue(deviceMap.containsKey("product3:variant3")); |
| assertEquals(3, deviceMap.get("product3:variant3").size()); |
| } |
| |
| @Test |
| public void testGetDevices_available() { |
| final List<DeviceDescriptor> deviceList = new ArrayList<>(); |
| deviceList.add(createDevice("product1", "variant1", DeviceAllocationState.Available)); |
| deviceList.add(createDevice("product2", "variant2", DeviceAllocationState.Available)); |
| deviceList.add(createDevice("product2", "variant2", DeviceAllocationState.Allocated)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Available)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Allocated)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Unavailable)); |
| when(mMockDeviceManager.listAllDevices()).thenReturn(deviceList); |
| |
| final MultiMap<String, DeviceDescriptor> deviceMap = |
| mScheduler.getDevices(mMockDeviceManager, true); |
| |
| assertTrue(deviceMap.containsKey("product1:variant1")); |
| assertEquals(1, deviceMap.get("product1:variant1").size()); |
| assertTrue(deviceMap.containsKey("product2:variant2")); |
| assertEquals(1, deviceMap.get("product2:variant2").size()); |
| assertTrue(deviceMap.containsKey("product3:variant3")); |
| assertEquals(1, deviceMap.get("product3:variant3").size()); |
| } |
| |
| @Test |
| public void testGetDevices_LocalhostIpDevices() { |
| final List<DeviceDescriptor> deviceList = new ArrayList<>(); |
| deviceList.add( |
| createDevice( |
| "127.0.0.1:101", "product1", "variant1", DeviceAllocationState.Available)); |
| deviceList.add( |
| createDevice( |
| "127.0.0.1:102", "product1", "variant1", DeviceAllocationState.Available)); |
| deviceList.add(createDevice("product2", "variant2", DeviceAllocationState.Allocated)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Available)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Unavailable)); |
| when(mMockDeviceManager.listAllDevices()).thenReturn(deviceList); |
| |
| final MultiMap<String, DeviceDescriptor> deviceMap = |
| mScheduler.getDevices(mMockDeviceManager, true); |
| |
| assertFalse(deviceMap.containsKey("product1:variant1")); |
| assertFalse(deviceMap.containsKey("product2:variant2")); |
| assertTrue(deviceMap.containsKey("product3:variant3")); |
| assertEquals(1, deviceMap.get("product3:variant3").size()); |
| } |
| |
| @Test |
| public void testGetDevices_NoAvailableDevices() { |
| final List<DeviceDescriptor> deviceList = new ArrayList<>(); |
| deviceList.add(createDevice("product1", "variant1", DeviceAllocationState.Allocated)); |
| deviceList.add(createDevice("product2", "variant2", DeviceAllocationState.Unavailable)); |
| deviceList.add(createDevice("product3", "variant3", DeviceAllocationState.Ignored)); |
| when(mMockDeviceManager.listAllDevices()).thenReturn(deviceList); |
| |
| final MultiMap<String, DeviceDescriptor> deviceMap = |
| mScheduler.getDevices(mMockDeviceManager, true); |
| |
| assertTrue(deviceMap.isEmpty()); |
| } |
| |
| private JSONObject createCommandTask( |
| String requestId, String commandId, String taskId, String attemptId, String commandLine) |
| throws JSONException { |
| JSONObject ret = new JSONObject(); |
| ret.put(REQUEST_ID, requestId); |
| ret.put(COMMAND_ID, commandId); |
| ret.put(ATTEMPT_ID, attemptId); |
| ret.put(TASK_ID, taskId); |
| ret.put("command_line", commandLine); |
| return ret; |
| } |
| |
| private JSONObject createLeaseResponse(JSONObject... tasks) throws JSONException { |
| JSONObject response = new JSONObject(); |
| JSONArray array = new JSONArray(); |
| for (JSONObject task : tasks) { |
| array.put(task); |
| } |
| response.put("tasks", array); |
| return response; |
| } |
| |
| @Test |
| public void testFetchHostCommands() throws Exception { |
| // Create some devices to fetch tasks for |
| mMockClusterOptions.getDeviceGroup().put("group1", "s1"); |
| mMockClusterOptions.getDeviceGroup().put("group1", "s2"); |
| DeviceDescriptor d1 = |
| createDevice("s1", "product1", "variant1", DeviceAllocationState.Available); |
| DeviceDescriptor d2 = |
| createDevice("s2", "product2", "variant2", DeviceAllocationState.Available); |
| DeviceDescriptor d3 = |
| createDevice("s3", "product2", "variant2", DeviceAllocationState.Available); |
| String runTarget1 = "product1:variant1"; |
| String runTarget2 = "product2:variant2"; |
| final MultiMap<String, DeviceDescriptor> deviceMap = new MultiMap<>(); |
| deviceMap.put(runTarget1, d1); |
| deviceMap.put(runTarget2, d2); |
| deviceMap.put(runTarget2, d3); |
| |
| mMockClusterOptions.getNextClusterIds().add("cluster2"); |
| mMockClusterOptions.getNextClusterIds().add("cluster3"); |
| |
| ArgumentCaptor<JSONObject> capture = ArgumentCaptor.forClass(JSONObject.class); |
| // Create some mock responses for the expected REST API calls |
| JSONObject product1Response = |
| createLeaseResponse(createCommandTask("1", "2", "3", "4", "command line 1")); |
| when(mMockApiHelper.execute( |
| Mockito.eq("POST"), |
| AdditionalMatchers.aryEq(new String[] {"tasks", "leasehosttasks"}), |
| Mockito.eq( |
| ImmutableMap.of( |
| "cluster", |
| CLUSTER_ID, |
| "hostname", |
| ClusterHostUtil.getHostName(), |
| "num_tasks", |
| Integer.toString(3))), |
| capture.capture())) |
| .thenReturn(buildHttpResponse(product1Response.toString())); |
| when(mMockHostOptions.getAvailablePermits(PermitLimitType.CONCURRENT_FLASHER)) |
| .thenReturn(1); |
| when(mMockHostOptions.getAvailablePermits(PermitLimitType.CONCURRENT_DOWNLOAD)) |
| .thenReturn(5); |
| // Actually fetch commands |
| |
| final List<ClusterCommand> commands = mScheduler.fetchHostCommands(deviceMap); |
| |
| // Verity the http request body is correct. |
| JSONArray deviceInfos = capture.getValue().getJSONArray("device_infos"); |
| JSONArray clusterIds = capture.getValue().getJSONArray("next_cluster_ids"); |
| assertEquals("group1", deviceInfos.getJSONObject(0).get("group_name")); |
| assertEquals("s1", deviceInfos.getJSONObject(0).get("device_serial")); |
| assertEquals("group1", deviceInfos.getJSONObject(1).get("group_name")); |
| assertEquals("s2", deviceInfos.getJSONObject(1).get("device_serial")); |
| assertFalse(deviceInfos.getJSONObject(2).has("group_name")); |
| assertEquals("s3", deviceInfos.getJSONObject(2).get("device_serial")); |
| assertEquals("cluster2", clusterIds.getString(0)); |
| assertEquals("cluster3", clusterIds.getString(1)); |
| |
| // expect 1 command allocated per device type based on availability and fetching algorithm |
| assertEquals("commands size mismatch", 1, commands.size()); |
| ClusterCommand command = commands.get(0); |
| assertEquals("1", command.getRequestId()); |
| assertEquals("2", command.getCommandId()); |
| assertEquals("3", command.getTaskId()); |
| assertEquals("4", command.getAttemptId()); |
| assertEquals("command line 1", command.getCommandLine()); |
| } |
| |
| @Test |
| public void testFetchHostCommands_withFlashingPermitCheck() throws Exception { |
| // Create some devices to fetch tasks for |
| DeviceDescriptor d1 = |
| createDevice("s1", "product1", "variant1", DeviceAllocationState.Available); |
| String runTarget1 = "product1:variant1"; |
| DeviceDescriptor d2 = |
| createDevice("s2", "product1", "variant1", DeviceAllocationState.Available); |
| final MultiMap<String, DeviceDescriptor> deviceMap = new MultiMap<>(); |
| deviceMap.put(runTarget1, d1); |
| deviceMap.put(runTarget1, d2); |
| |
| mMockClusterOptions.getNextClusterIds().add("cluster2"); |
| mMockClusterOptions.setCheckFlashingPermitsLease(true); |
| ArgumentCaptor<JSONObject> capture = ArgumentCaptor.forClass(JSONObject.class); |
| // Create some mock responses for the expected REST API calls |
| JSONObject product1Response = |
| createLeaseResponse(createCommandTask("1", "2", "3", "4", "command line 1")); |
| when(mMockApiHelper.execute( |
| Mockito.eq("POST"), |
| AdditionalMatchers.aryEq(new String[] {"tasks", "leasehosttasks"}), |
| Mockito.eq( |
| ImmutableMap.of( |
| "cluster", |
| CLUSTER_ID, |
| "hostname", |
| ClusterHostUtil.getHostName(), |
| "num_tasks", |
| Integer.toString(1))), |
| capture.capture())) |
| .thenReturn(buildHttpResponse(product1Response.toString())); |
| when(mMockHostOptions.getAvailablePermits(PermitLimitType.CONCURRENT_FLASHER)) |
| .thenReturn(1); |
| when(mMockHostOptions.getAvailablePermits(PermitLimitType.CONCURRENT_DOWNLOAD)) |
| .thenReturn(5); |
| |
| // Actually fetch commands |
| final List<ClusterCommand> commands = mScheduler.fetchHostCommands(deviceMap); |
| |
| assertEquals(1, commands.size()); |
| ClusterCommand command = commands.get(0); |
| assertEquals("1", command.getRequestId()); |
| assertEquals("2", command.getCommandId()); |
| assertEquals("3", command.getTaskId()); |
| assertEquals("4", command.getAttemptId()); |
| assertEquals("command line 1", command.getCommandLine()); |
| } |
| |
| /** Test when the run target pattern contains repeated patterns. */ |
| @Test |
| public void testRepeatedPattern() { |
| String format = "foo-{PRODUCT}-{PRODUCT}:{PRODUCT_VARIANT}"; |
| mMockClusterOptions.setRunTargetFormat(format); |
| DeviceDescriptor device = |
| new DeviceDescriptor( |
| DEVICE_SERIAL, |
| false, |
| DeviceAllocationState.Available, |
| "product", |
| "productVariant", |
| "sdkVersion", |
| "buildId", |
| "batteryLevel"); |
| assertEquals( |
| "foo-product-product:productVariant", |
| ClusterHostUtil.getRunTarget(device, format, null)); |
| } |
| |
| /** Test default behavior when device serial is not set for command task. */ |
| @Test |
| public void testExecCommandsWithNoSerials() { |
| List<ClusterCommand> cmds = new ArrayList<>(); |
| ClusterCommand cmd = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| cmds.add(cmd); |
| mScheduler.execCommands(cmds); |
| assertEquals(CMD_LINE, cmds.get(0).getCommandLine()); |
| assertArrayEquals(new String[] {CMD_LINE}, getExecCommandArgs()); |
| } |
| |
| /** If device serial is specified for a command task append serial to it. */ |
| @Test |
| public void testExecCommandWithSerial() { |
| List<ClusterCommand> cmds = new ArrayList<>(); |
| ClusterCommand cmd = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| cmd.setTargetDeviceSerials(ArrayUtil.list("deviceSerial")); |
| cmds.add(cmd); |
| mScheduler.execCommands(cmds); |
| assertEquals(CMD_LINE, cmds.get(0).getCommandLine()); |
| assertArrayEquals( |
| new String[] {CMD_LINE, "--serial", "deviceSerial"}, getExecCommandArgs()); |
| } |
| |
| /** |
| * If a unique device serial (one with a hostname prefix) is specified for a command task, |
| * convert it to a local device serial before appending it. |
| */ |
| @Test |
| public void testExecCommandWithVirtualDeviceSerial() { |
| List<ClusterCommand> cmds = new ArrayList<>(); |
| ClusterCommand cmd = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| cmd.setTargetDeviceSerials( |
| ArrayUtil.list(ClusterHostUtil.getHostName() + ":emulator-5554")); |
| cmds.add(cmd); |
| mScheduler.execCommands(cmds); |
| assertEquals(CMD_LINE, cmds.get(0).getCommandLine()); |
| assertArrayEquals( |
| new String[] {CMD_LINE, "--serial", "emulator-5554"}, getExecCommandArgs()); |
| } |
| |
| /** Multiple serials specified for a command task. */ |
| @Test |
| public void testExecCommandWithMultipleSerials() { |
| List<ClusterCommand> cmds = new ArrayList<>(); |
| ClusterCommand cmd = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| cmd.setTargetDeviceSerials( |
| ArrayUtil.list("deviceSerial0", "deviceSerial1", "deviceSerial2")); |
| cmds.add(cmd); |
| mScheduler.execCommands(cmds); |
| assertEquals(CMD_LINE, cmds.get(0).getCommandLine()); |
| assertArrayEquals( |
| new String[] { |
| CMD_LINE, |
| "--serial", |
| "deviceSerial0", |
| "--serial", |
| "deviceSerial1", |
| "--serial", |
| "deviceSerial2" |
| }, |
| getExecCommandArgs()); |
| } |
| |
| /** Multiple serials specified for multiple commands. */ |
| @Test |
| public void testExecCommandWithMultipleCommandsAndSerials() { |
| List<String> serials = ArrayUtil.list("deviceSerial0", "deviceSerial1", "deviceSerial2"); |
| List<ClusterCommand> cmds = new ArrayList<>(); |
| ClusterCommand cmd0 = new ClusterCommand("command_id0", "task_id0", CMD_LINE); |
| cmd0.setTargetDeviceSerials(serials); |
| cmds.add(cmd0); |
| ClusterCommand cmd1 = new ClusterCommand("command_id1", "task_id1", "test1"); |
| cmd1.setTargetDeviceSerials(serials); |
| cmds.add(cmd1); |
| mScheduler.execCommands(cmds); |
| assertEquals(CMD_LINE, cmds.get(0).getCommandLine()); |
| assertEquals("test1", cmds.get(1).getCommandLine()); |
| assertArrayEquals( |
| new String[] { |
| "test1", |
| "--serial", |
| "deviceSerial0", |
| "--serial", |
| "deviceSerial1", |
| "--serial", |
| "deviceSerial2" |
| }, |
| getExecCommandArgs()); |
| assertArrayEquals( |
| new String[] { |
| CMD_LINE, |
| "--serial", |
| "deviceSerial0", |
| "--serial", |
| "deviceSerial1", |
| "--serial", |
| "deviceSerial2" |
| }, |
| getExecCommandArgs()); |
| } |
| |
| @Test |
| public void testInvocationEventHandler() { |
| ClusterCommand mockCommand = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| IInvocationContext context = new InvocationContext(); |
| ITestDevice mockTestDevice = mock(ITestDevice.class); |
| when(mockTestDevice.getSerialNumber()).thenReturn(DEVICE_SERIAL); |
| when(mockTestDevice.getIDevice()).thenReturn(new StubDevice(DEVICE_SERIAL)); |
| context.addAllocatedDevice("", mockTestDevice); |
| IBuildInfo mockBuildInfo = mock(IBuildInfo.class); |
| context.addDeviceBuildInfo("", mockBuildInfo); |
| ClusterCommandScheduler.InvocationEventHandler handler = |
| mScheduler.new InvocationEventHandler(mockCommand); |
| mMockClusterOptions.setCollectEarlyTestSummary(true); |
| |
| handler.invocationInitiated(context); |
| List<TestSummary> summaries = new ArrayList<>(); |
| summaries.add( |
| new TestSummary(new TestSummary.TypedString("http://uri", TestSummary.Type.URI))); |
| handler.putEarlySummary(summaries); |
| handler.putSummary(summaries); |
| handler.invocationStarted(context); |
| handler.testRunStarted("test run", 1); |
| handler.testStarted(new TestDescription("class", CMD_LINE)); |
| handler.testEnded(new TestDescription("class", CMD_LINE), new HashMap<String, Metric>()); |
| handler.testRunEnded(10L, new HashMap<String, Metric>()); |
| handler.invocationEnded(100L); |
| context.addAllocatedDevice(DEVICE_SERIAL, mockTestDevice); |
| context.addInvocationAttribute(InvocationMetricKey.FETCH_BUILD.toString(), "100"); |
| context.addInvocationAttribute(InvocationMetricKey.SETUP.toString(), "200"); |
| context.addInvocationAttribute(InvocationMetricKey.DEVICE_LOST_DETECTED.toString(), "1"); |
| Map<ITestDevice, FreeDeviceState> releaseMap = new HashMap<>(); |
| releaseMap.put(mockTestDevice, FreeDeviceState.AVAILABLE); |
| handler.invocationComplete(context, releaseMap); |
| |
| // Custom matchers are simpler than captures as there are events not of interest. |
| class IsCustomInvocationCompleted implements ArgumentMatcher<ClusterCommandEvent> { |
| @Override |
| public boolean matches(ClusterCommandEvent event) { |
| // Ensure we have not raised an unexpected error |
| return event.getType().equals(ClusterCommandEvent.Type.InvocationCompleted) |
| && (null == event.getData().get(ClusterCommandEvent.DATA_KEY_ERROR)) |
| && "0" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_FAILED_TEST_COUNT)) |
| && "1" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_PASSED_TEST_COUNT)) |
| && "100" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_FETCH_BUILD_TIME_MILLIS)) |
| && "200" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_SETUP_TIME_MILLIS)) |
| && "URI: http://uri\n" |
| .equals(event.getData().get(ClusterCommandEvent.DATA_KEY_SUMMARY)) |
| && "1" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_LOST_DEVICE_DETECTED)); |
| } |
| } |
| |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationInitiated())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationStarted())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationEnded())); |
| verify(mMockEventUploader).postEvent(argThat(new IsCustomInvocationCompleted())); |
| verify(mMockEventUploader, times(4)).flush(); |
| } |
| |
| /** Test that the error count is the proper one. */ |
| @Test |
| public void testInvocationEventHandler_counting() { |
| ClusterCommand mockCommand = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| IInvocationContext context = new InvocationContext(); |
| ITestDevice mockTestDevice = mock(ITestDevice.class); |
| when(mockTestDevice.getSerialNumber()).thenReturn(DEVICE_SERIAL); |
| when(mockTestDevice.getIDevice()).thenReturn(new StubDevice(DEVICE_SERIAL)); |
| IBuildInfo mockBuildInfo = mock(IBuildInfo.class); |
| context.addAllocatedDevice("", mockTestDevice); |
| context.addDeviceBuildInfo("", mockBuildInfo); |
| ClusterCommandScheduler.InvocationEventHandler handler = |
| mScheduler.new InvocationEventHandler(mockCommand); |
| |
| handler.invocationInitiated(context); |
| handler.invocationStarted(context); |
| handler.testRunStarted("test run", 1); |
| TestDescription tid = new TestDescription("class", CMD_LINE); |
| handler.testStarted(tid); |
| handler.testFailed(tid, "failed"); |
| handler.testEnded(tid, new HashMap<String, Metric>()); |
| TestDescription tid2 = new TestDescription("class", "test2"); |
| handler.testStarted(tid2); |
| handler.testAssumptionFailure(tid2, "I assume I failed"); |
| handler.testEnded(tid2, new HashMap<String, Metric>()); |
| handler.testRunEnded(10L, new HashMap<String, Metric>()); |
| handler.testRunStarted("failed test run", 1); |
| TestDescription tid3 = new TestDescription("class", "test3"); |
| handler.testStarted(tid3); |
| handler.testFailed(tid3, "test terminated without result"); |
| handler.testRunFailed("test runner crashed"); |
| handler.testRunEnded(10L, new HashMap<String, Metric>()); |
| handler.invocationEnded(100L); |
| context.addAllocatedDevice(DEVICE_SERIAL, mockTestDevice); |
| Map<ITestDevice, FreeDeviceState> releaseMap = new HashMap<>(); |
| releaseMap.put(mockTestDevice, FreeDeviceState.AVAILABLE); |
| handler.invocationComplete(context, releaseMap); |
| |
| // Custom matchers are simpler than captures as there are events not of interest. |
| class IsCustomInvocationCompleted implements ArgumentMatcher<ClusterCommandEvent> { |
| @Override |
| public boolean matches(ClusterCommandEvent event) { |
| // Ensure we have not raised an unexpected error |
| return event.getType().equals(ClusterCommandEvent.Type.InvocationCompleted) |
| && (null == event.getData().get(ClusterCommandEvent.DATA_KEY_ERROR)) |
| && "2" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_FAILED_TEST_COUNT)) |
| && "0" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_PASSED_TEST_COUNT)) |
| && "1" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_FAILED_TEST_RUN_COUNT)); |
| } |
| } |
| |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationInitiated())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationStarted())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationEnded())); |
| verify(mMockEventUploader).postEvent(argThat(new IsCustomInvocationCompleted())); |
| verify(mMockEventUploader, times(4)).flush(); |
| } |
| |
| @Test |
| public void testInvocationEventHandler_longTestRun() { |
| ClusterCommand mockCommand = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| IInvocationContext context = new InvocationContext(); |
| ITestDevice mockTestDevice = mock(ITestDevice.class); |
| when(mockTestDevice.getSerialNumber()).thenReturn(DEVICE_SERIAL); |
| when(mockTestDevice.getIDevice()).thenReturn(new StubDevice(DEVICE_SERIAL)); |
| context.addAllocatedDevice("", mockTestDevice); |
| IBuildInfo mockBuildInfo = mock(IBuildInfo.class); |
| context.addDeviceBuildInfo("", mockBuildInfo); |
| ClusterCommandScheduler.InvocationEventHandler handler = |
| mScheduler.new InvocationEventHandler(mockCommand); |
| |
| handler.invocationInitiated(context); |
| handler.invocationStarted(context); |
| handler.testRunStarted("test run", 1); |
| handler.testStarted(new TestDescription("class", CMD_LINE)); |
| handler.testEnded(new TestDescription("class", CMD_LINE), new HashMap<String, Metric>()); |
| handler.testRunEnded(10L, new HashMap<String, Metric>()); |
| handler.invocationEnded(100L); |
| context.addAllocatedDevice(DEVICE_SERIAL, mockTestDevice); |
| Map<ITestDevice, FreeDeviceState> releaseMap = new HashMap<>(); |
| releaseMap.put(mockTestDevice, FreeDeviceState.AVAILABLE); |
| handler.invocationComplete(context, releaseMap); |
| |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationInitiated())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationStarted())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationEnded())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationCompleted())); |
| verify(mMockEventUploader, times(4)).flush(); |
| } |
| |
| @Test |
| public void testInvocationEventHandler_multiDevice() { |
| ClusterCommand mockCommand = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| IInvocationContext context = new InvocationContext(); |
| |
| ITestDevice mockTestDevice1 = mock(ITestDevice.class); |
| when(mockTestDevice1.getSerialNumber()).thenReturn(DEVICE_SERIAL); |
| when(mockTestDevice1.getIDevice()).thenReturn(new StubDevice(DEVICE_SERIAL)); |
| context.addAllocatedDevice("device1", mockTestDevice1); |
| ITestDevice mockTestDevice2 = mock(ITestDevice.class); |
| when(mockTestDevice2.getSerialNumber()).thenReturn("s2"); |
| when(mockTestDevice2.getIDevice()).thenReturn(new StubDevice("s2")); |
| context.addAllocatedDevice("device2", mockTestDevice2); |
| IBuildInfo mockBuildInfo = mock(IBuildInfo.class); |
| context.addDeviceBuildInfo("", mockBuildInfo); |
| ClusterCommandScheduler.InvocationEventHandler handler = |
| mScheduler.new InvocationEventHandler(mockCommand); |
| |
| handler.invocationInitiated(context); |
| |
| // Custom matchers are simpler than captures as there are events not of interest. |
| class IsCustomInvocationInitiated implements ArgumentMatcher<ClusterCommandEvent> { |
| @Override |
| public boolean matches(ClusterCommandEvent event) { |
| Set<String> deviceSerials = new HashSet<>(); |
| deviceSerials.add(DEVICE_SERIAL); |
| deviceSerials.add("s2"); |
| return event.getType().equals(ClusterCommandEvent.Type.InvocationInitiated) |
| && deviceSerials.equals(event.getDeviceSerials()); |
| } |
| } |
| verify(mMockEventUploader).postEvent(argThat(new IsCustomInvocationInitiated())); |
| verify(mMockEventUploader).flush(); |
| } |
| |
| @Test |
| public void testInvocationEventHandler_withSubprocessCommandException() { |
| ClusterCommand mockCommand = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| IInvocationContext context = new InvocationContext(); |
| ITestDevice mockTestDevice = mock(ITestDevice.class); |
| when(mockTestDevice.getSerialNumber()).thenReturn(DEVICE_SERIAL); |
| when(mockTestDevice.getIDevice()).thenReturn(new StubDevice(DEVICE_SERIAL)); |
| context.addAllocatedDevice("", mockTestDevice); |
| IBuildInfo mockBuildInfo = mock(IBuildInfo.class); |
| context.addDeviceBuildInfo("", mockBuildInfo); |
| ClusterCommandScheduler.InvocationEventHandler handler = |
| mScheduler.new InvocationEventHandler(mockCommand); |
| mMockClusterOptions.setCollectEarlyTestSummary(true); |
| |
| handler.invocationInitiated(context); |
| List<TestSummary> summaries = new ArrayList<>(); |
| summaries.add( |
| new TestSummary(new TestSummary.TypedString("http://uri", TestSummary.Type.URI))); |
| handler.putEarlySummary(summaries); |
| handler.putSummary(summaries); |
| handler.invocationStarted(context); |
| handler.invocationFailed( |
| new SubprocessCommandException( |
| "error_message", new Throwable("subprocess_command_error_message"))); |
| handler.invocationEnded(100L); |
| context.addAllocatedDevice(DEVICE_SERIAL, mockTestDevice); |
| Map<ITestDevice, FreeDeviceState> releaseMap = new HashMap<>(); |
| releaseMap.put(mockTestDevice, FreeDeviceState.AVAILABLE); |
| handler.invocationComplete(context, releaseMap); |
| |
| // Custom matchers are simpler than captures as there are events not of interest. |
| class IsCustomInvocationCompleted implements ArgumentMatcher<ClusterCommandEvent> { |
| @Override |
| public boolean matches(ClusterCommandEvent event) { |
| // Ensure we have not raised an unexpected error |
| return event.getType().equals(ClusterCommandEvent.Type.InvocationCompleted) |
| && ((String) event.getData().get(ClusterCommandEvent.DATA_KEY_ERROR)) |
| .contains("SubprocessCommandException") |
| && "subprocess_command_error_message" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_SUBPROCESS_COMMAND_ERROR)) |
| && "0" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_FAILED_TEST_COUNT)) |
| && "0" |
| .equals( |
| event.getData() |
| .get( |
| ClusterCommandEvent |
| .DATA_KEY_PASSED_TEST_COUNT)) |
| && "URI: http://uri\n" |
| .equals(event.getData().get(ClusterCommandEvent.DATA_KEY_SUMMARY)); |
| } |
| } |
| |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationInitiated())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationStarted())); |
| verify(mMockEventUploader).postEvent(argThat(new IsInvocationEnded())); |
| verify(mMockEventUploader).postEvent(argThat(new IsCustomInvocationCompleted())); |
| verify(mMockEventUploader, times(4)).flush(); |
| } |
| |
| /** |
| * Test that when dry-run is used we validate the config and no ConfigurationException gets |
| * thrown. |
| */ |
| @Test |
| public void testExecCommandsWithDryRun() { |
| mScheduler = |
| new ClusterCommandScheduler() { |
| @Override |
| public IClusterOptions getClusterOptions() { |
| return mMockClusterOptions; |
| } |
| |
| @Override |
| IClusterClient getClusterClient() { |
| return mMockClusterClient; |
| } |
| |
| @Override |
| public void execCommand(IScheduledInvocationListener listener, String[] args) |
| throws ConfigurationException, NoDeviceException { |
| ArrayList<String> execCmdArgs = new ArrayList<>(); |
| for (String arg : args) { |
| execCmdArgs.add(arg); |
| } |
| mExecCmdArgs.push(execCmdArgs); |
| } |
| |
| @Override |
| protected IKeyStoreClient getKeyStoreClient() { |
| return new StubKeyStoreClient(); |
| } |
| }; |
| ClusterCommand cmd = new ClusterCommand(COMMAND_ID, TASK_ID, "empty --dry-run"); |
| mScheduler.execCommands(Arrays.asList(cmd)); |
| assertEquals("empty --dry-run", cmd.getCommandLine()); |
| // Nothing gets executed |
| assertTrue(mExecCmdArgs.isEmpty()); |
| } |
| |
| // Helper class for more functional like tests. |
| private class TestableClusterCommandScheduler extends ClusterCommandScheduler { |
| |
| private IDeviceManager manager = new MockDeviceManager(1); |
| // track whether stopInvocation was called |
| private boolean stopInvocationCalled; |
| |
| @Override |
| public IClusterOptions getClusterOptions() { |
| return mMockClusterOptions; |
| } |
| |
| @Override |
| IClusterClient getClusterClient() { |
| return mMockClusterClient; |
| } |
| |
| @Override |
| protected boolean dryRunCommand(final InvocationEventHandler handler, String[] args) { |
| return false; |
| } |
| |
| @Override |
| protected IDeviceManager getDeviceManager() { |
| return manager; |
| } |
| |
| // Direct getter to avoid making getDeviceManager public. |
| public IDeviceManager getTestManager() { |
| return manager; |
| } |
| |
| @Override |
| protected void initLogging() { |
| // ignore |
| } |
| |
| @Override |
| protected void cleanUp() { |
| // ignore |
| } |
| |
| @Override |
| public boolean stopInvocation(int invocationId, String cause) { |
| return (stopInvocationCalled = true); |
| } |
| |
| public boolean wasStopInvocationCalled() { |
| return stopInvocationCalled; |
| } |
| } |
| |
| /** Test that when a provider returns a null build, we still handle it gracefully. */ |
| @Test |
| public void testExecCommands_nullBuild() throws Exception { |
| try { |
| GlobalConfiguration.createGlobalConfiguration(new String[] {}); |
| } catch (IllegalStateException e) { |
| // in case Global config is already initialized. |
| } |
| File tmpLogDir = FileUtil.createTempDir("clusterschedulertest"); |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| List<ClusterCommand> cmds = new ArrayList<>(); |
| // StubBuildProvider of empty.xml can return a null instead of build with --return-null |
| ClusterCommand cmd = |
| new ClusterCommand( |
| COMMAND_ID, |
| TASK_ID, |
| "empty --return-null " + "--log-file-path " + tmpLogDir.getAbsolutePath()); |
| cmds.add(cmd); |
| IDeviceManager m = scheduler.getTestManager(); |
| scheduler.start(); |
| try { |
| // execCommands is going to allocate a device to execute the command. |
| scheduler.execCommands(cmds); |
| assertEquals(0, ((MockDeviceManager) m).getQueueOfAvailableDeviceSize()); |
| assertEquals( |
| "empty --return-null --log-file-path " + tmpLogDir.getAbsolutePath(), |
| cmds.get(0).getCommandLine()); |
| scheduler.shutdownOnEmpty(); |
| scheduler.join(2000); |
| // Give it a bit of time as the device re-appearing can be slow. |
| RunUtil.getDefault().sleep(200L); |
| // There is only one device so allocation should succeed if device was released. |
| assertEquals(1, ((MockDeviceManager) m).getQueueOfAvailableDeviceSize()); |
| ITestDevice device = m.allocateDevice(); |
| assertNotNull(device); |
| } finally { |
| scheduler.shutdown(); |
| File zipLog = ZipUtil.createZip(tmpLogDir); |
| try (FileInputStreamSource source = new FileInputStreamSource(zipLog, true)) { |
| mTestLog.addTestLog("testExecCommands_nullBuild", LogDataType.ZIP, source); |
| } |
| FileUtil.recursiveDelete(tmpLogDir); |
| } |
| } |
| |
| /** Test that when a provider throws a build error retrieval, we still handle it gracefully. */ |
| @Test |
| public void testExecCommands_buildRetrievalError() throws Exception { |
| try { |
| // we need to initialize the GlobalConfiguration when running directly in IDE. |
| GlobalConfiguration.createGlobalConfiguration(new String[] {}); |
| } catch (IllegalStateException e) { |
| // in case Global config is already initialized, when running in the infra. |
| } |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| File tmpLogDir = FileUtil.createTempDir("clusterschedulertest"); |
| List<ClusterCommand> cmds = new ArrayList<>(); |
| // StubBuildProvider of empty.xml can throw a build error if requested via |
| // --throw-build-error |
| ClusterCommand cmd = |
| new ClusterCommand( |
| COMMAND_ID, |
| TASK_ID, |
| "empty --throw-build-error --log-file-path " + tmpLogDir.getAbsolutePath()); |
| cmds.add(cmd); |
| IDeviceManager m = scheduler.getTestManager(); |
| scheduler.start(); |
| try { |
| scheduler.execCommands(cmds); |
| assertEquals(0, ((MockDeviceManager) m).getQueueOfAvailableDeviceSize()); |
| scheduler.shutdownOnEmpty(); |
| scheduler.join(5000); |
| // Give it a bit of time as the device re-appearing can be slow. |
| RunUtil.getDefault().sleep(200L); |
| // There is only one device so allocation should succeed if device was released. |
| assertEquals(1, ((MockDeviceManager) m).getQueueOfAvailableDeviceSize()); |
| ITestDevice device = m.allocateDevice(); |
| assertNotNull(device); |
| } finally { |
| scheduler.shutdown(); |
| FileUtil.recursiveDelete(tmpLogDir); |
| } |
| } |
| |
| private ClusterCommand createMockManagedCommand(int numDevices) { |
| return createMockManagedCommand(numDevices, null, null); |
| } |
| |
| private ClusterCommand createMockManagedCommand( |
| int numDevices, Integer shardCount, Integer shardIndex) { |
| ClusterCommand cmd = |
| new ClusterCommand( |
| REQUEST_ID, |
| COMMAND_ID, |
| TASK_ID, |
| "command", |
| UUID.randomUUID().toString(), |
| RequestType.MANAGED, |
| shardCount, |
| shardIndex); |
| for (int i = 0; i < numDevices; i++) { |
| cmd.getTargetDeviceSerials().add(String.format("serial%d", i)); |
| } |
| return cmd; |
| } |
| |
| private TestEnvironment createMockTestEnvironment() { |
| TestEnvironment testEnvironment = new TestEnvironment(); |
| testEnvironment.addEnvVar("ENV1", "env1"); |
| testEnvironment.addEnvVar("ENV2", "env2"); |
| testEnvironment.addSetupScripts("script1"); |
| testEnvironment.addSetupScripts("script2"); |
| testEnvironment.addJvmOption("option1"); |
| testEnvironment.addJavaProperty("JAVA1", "java1"); |
| testEnvironment.addJavaProperty("JAVA2", "java2"); |
| testEnvironment.setOutputFileUploadUrl("output_file_upload_url"); |
| testEnvironment.addOutputFilePattern("output_file_pattern1"); |
| testEnvironment.addOutputFilePattern("output_file_pattern2"); |
| return testEnvironment; |
| } |
| |
| private List<TestResource> createMockTestResources() { |
| List<TestResource> testResources = new ArrayList<TestResource>(); |
| testResources.add(new TestResource("name1", "url2")); |
| testResources.add( |
| new TestResource("name2", "url2", true, "dir2", false, Arrays.asList("file2"))); |
| return testResources; |
| } |
| |
| private void verifyConfig( |
| IConfiguration config, |
| ClusterCommand cmd, |
| TestEnvironment testEnvironment, |
| List<TestResource> testResources, |
| File workDir) { |
| List<IDeviceConfiguration> deviceConfigs = config.getDeviceConfig(); |
| assertEquals(cmd.getTargetDeviceSerials().size(), deviceConfigs.size()); |
| for (int i = 0; i < cmd.getTargetDeviceSerials().size(); i++) { |
| String serial = |
| ClusterHostUtil.getLocalDeviceSerial(cmd.getTargetDeviceSerials().get(i)); |
| IDeviceConfiguration deviceConfig = deviceConfigs.get(i); |
| Collection<String> serials = deviceConfig.getDeviceRequirements().getSerials(null); |
| assertTrue(serials.size() == 1 && serials.contains(serial)); |
| |
| ClusterBuildProvider buildProvider = |
| (ClusterBuildProvider) deviceConfig.getBuildProvider(); |
| List<TestResource> providerTestResources = buildProvider.getTestResources(); |
| assertEquals(testResources.size(), providerTestResources.size()); |
| Map<String, TestResource> providerTestResourceMap = |
| providerTestResources.stream() |
| .collect(Collectors.toMap(TestResource::getName, Function.identity())); |
| for (TestResource r : testResources) { |
| TestResource pr = providerTestResourceMap.get(r.getName()); |
| assertEquals(r.getUrl(), pr.getUrl()); |
| assertEquals(r.getDecompress(), pr.getDecompress()); |
| assertEquals(r.getDecompressDir(), pr.getDecompressDir()); |
| assertEquals(r.mountZip(), pr.mountZip()); |
| assertEquals(r.getDecompressFiles(), pr.getDecompressFiles()); |
| } |
| } |
| |
| ClusterCommandLauncher test = (ClusterCommandLauncher) config.getTests().get(0); |
| assertEquals(cmd.getCommandLine(), test.getCommandLine()); |
| |
| Map<String, String> envVars = new TreeMap<>(); |
| envVars.put("TF_WORK_DIR", workDir.getAbsolutePath()); |
| envVars.putAll(testEnvironment.getEnvVars()); |
| assertEquals(envVars, test.getEnvVars()); |
| assertEquals(testEnvironment.getJvmOptions(), test.getJvmOptions()); |
| assertEquals(testEnvironment.getSetupScripts(), test.getSetupScripts()); |
| assertEquals(testEnvironment.getJavaProperties(), test.getJavaProperties()); |
| assertEquals(testEnvironment.useSubprocessReporting(), test.useSubprocessReporting()); |
| ClusterLogSaver logSaver = (ClusterLogSaver) config.getLogSaver(); |
| assertEquals(cmd.getAttemptId(), logSaver.getAttemptId()); |
| assertEquals( |
| String.format( |
| "%s/%s/%s/", |
| testEnvironment.getOutputFileUploadUrl(), |
| cmd.getCommandId(), |
| cmd.getAttemptId()), |
| logSaver.getOutputFileUploadUrl()); |
| assertEquals(testEnvironment.getOutputFilePatterns(), logSaver.getOutputFilePatterns()); |
| |
| // Verify all Tradefed config objects. |
| for (TradefedConfigObject.Type type : TradefedConfigObject.Type.values()) { |
| String typeName; |
| switch (type) { |
| case TARGET_PREPARER: |
| typeName = Configuration.TARGET_PREPARER_TYPE_NAME; |
| break; |
| case RESULT_REPORTER: |
| typeName = Configuration.RESULT_REPORTER_TYPE_NAME; |
| break; |
| default: |
| continue; |
| } |
| List<Object> configObjs; |
| if (TradefedConfigObject.Type.TARGET_PREPARER.equals(type)) { |
| configObjs = |
| new ArrayList<>( |
| config.getDeviceConfig().get(0).getAllObjectOfType(typeName)); |
| } else { |
| configObjs = new ArrayList<>(config.getAllConfigurationObjectsOfType(typeName)); |
| } |
| for (TradefedConfigObject configDef : testEnvironment.getTradefedConfigObjects()) { |
| if (configDef.getType() != type) { |
| continue; |
| } |
| // If configObjs is empty, it means we failed find objects for some configDefs. |
| assertFalse(configObjs.isEmpty()); |
| while (!configObjs.isEmpty()) { |
| Object configObj = configObjs.remove(0); |
| if (!configObj.getClass().getName().equals(configDef.getClassName())) { |
| continue; |
| } |
| } |
| } |
| } |
| } |
| |
| /** Tests an execution of a managed cluster command. */ |
| @Test |
| public void testExecManagedClusterCommand() throws Exception { |
| File workDir = null; |
| try { |
| ClusterCommand cmd = createMockManagedCommand(1); |
| workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId()); |
| TestEnvironment testEnvironment = createMockTestEnvironment(); |
| List<TestResource> testResources = createMockTestResources(); |
| TestContext testContext = new TestContext(); |
| mMockClusterClient = Mockito.spy(mMockClusterClient); |
| Mockito.doReturn(testEnvironment) |
| .when(mMockClusterClient) |
| .getTestEnvironment(REQUEST_ID); |
| Mockito.doReturn(testResources).when(mMockClusterClient).getTestResources(REQUEST_ID); |
| Mockito.doReturn(testContext) |
| .when(mMockClusterClient) |
| .getTestContext(REQUEST_ID, COMMAND_ID); |
| InvocationEventHandler invocationEventHandler = |
| mScheduler.new InvocationEventHandler(cmd); |
| |
| mScheduler.execManagedClusterCommand(cmd, invocationEventHandler); |
| |
| String[] args = getExecCommandArgs(); |
| assertTrue(args.length > 0); |
| IConfiguration config = |
| ConfigurationFactory.getInstance().createConfigurationFromArgs(args); |
| verifyConfig(config, cmd, testEnvironment, testResources, workDir); |
| } finally { |
| if (workDir != null) { |
| // Clean up work directory |
| FileUtil.recursiveDelete(workDir); |
| } |
| } |
| } |
| |
| /** Tests an execution of a managed cluster command. */ |
| @Test |
| public void testExecManagedClusterCommand_virtualDeviceTest() throws Exception { |
| File workDir = null; |
| try { |
| ClusterCommand cmd = createMockManagedCommand(1); |
| cmd.setTargetDeviceSerials( |
| ArrayUtil.list(ClusterHostUtil.getHostName() + ":emulator-5554")); |
| workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId()); |
| TestEnvironment testEnvironment = createMockTestEnvironment(); |
| List<TestResource> testResources = createMockTestResources(); |
| TestContext testContext = new TestContext(); |
| mMockClusterClient = Mockito.spy(mMockClusterClient); |
| Mockito.doReturn(testEnvironment) |
| .when(mMockClusterClient) |
| .getTestEnvironment(REQUEST_ID); |
| Mockito.doReturn(testResources).when(mMockClusterClient).getTestResources(REQUEST_ID); |
| Mockito.doReturn(testContext) |
| .when(mMockClusterClient) |
| .getTestContext(REQUEST_ID, COMMAND_ID); |
| InvocationEventHandler invocationEventHandler = |
| mScheduler.new InvocationEventHandler(cmd); |
| |
| mScheduler.execManagedClusterCommand(cmd, invocationEventHandler); |
| |
| String[] args = getExecCommandArgs(); |
| assertTrue(args.length > 0); |
| IConfiguration config = |
| ConfigurationFactory.getInstance().createConfigurationFromArgs(args); |
| verifyConfig(config, cmd, testEnvironment, testResources, workDir); |
| } finally { |
| if (workDir != null) { |
| // Clean up work directory |
| FileUtil.recursiveDelete(workDir); |
| } |
| } |
| } |
| |
| /** Tests an execution of a managed cluster command for multiple devices. */ |
| @Test |
| public void testExecManagedClusterCommand_multiDeviceTest() throws Exception { |
| File workDir = null; |
| try { |
| ClusterCommand cmd = createMockManagedCommand(5); |
| workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId()); |
| TestEnvironment testEnvironment = createMockTestEnvironment(); |
| List<TestResource> testResources = createMockTestResources(); |
| TestContext testContext = new TestContext(); |
| mMockClusterClient = Mockito.spy(mMockClusterClient); |
| Mockito.doReturn(testEnvironment) |
| .when(mMockClusterClient) |
| .getTestEnvironment(REQUEST_ID); |
| Mockito.doReturn(testResources).when(mMockClusterClient).getTestResources(REQUEST_ID); |
| Mockito.doReturn(testContext) |
| .when(mMockClusterClient) |
| .getTestContext(REQUEST_ID, COMMAND_ID); |
| InvocationEventHandler invocationEventHandler = |
| mScheduler.new InvocationEventHandler(cmd); |
| |
| mScheduler.execManagedClusterCommand(cmd, invocationEventHandler); |
| |
| String[] args = getExecCommandArgs(); |
| assertTrue(args.length > 0); |
| IConfiguration config = |
| ConfigurationFactory.getInstance().createConfigurationFromArgs(args); |
| verifyConfig(config, cmd, testEnvironment, testResources, workDir); |
| } finally { |
| if (workDir != null) { |
| // Clean up work directory |
| FileUtil.recursiveDelete(workDir); |
| } |
| } |
| } |
| |
| /** Tests an execution of a sharded managed cluster command. */ |
| @Test |
| public void testExecManagedClusterCommand_shardedTest() throws Exception { |
| File workDir = null; |
| try { |
| ClusterCommand cmd = createMockManagedCommand(1, 100, 0); |
| workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId()); |
| TestEnvironment testEnvironment = createMockTestEnvironment(); |
| List<TestResource> testResources = createMockTestResources(); |
| TestContext testContext = new TestContext(); |
| mMockClusterClient = Mockito.spy(mMockClusterClient); |
| Mockito.doReturn(testEnvironment) |
| .when(mMockClusterClient) |
| .getTestEnvironment(REQUEST_ID); |
| Mockito.doReturn(testResources).when(mMockClusterClient).getTestResources(REQUEST_ID); |
| Mockito.doReturn(testContext) |
| .when(mMockClusterClient) |
| .getTestContext(REQUEST_ID, COMMAND_ID); |
| InvocationEventHandler invocationEventHandler = |
| mScheduler.new InvocationEventHandler(cmd); |
| |
| mScheduler.execManagedClusterCommand(cmd, invocationEventHandler); |
| |
| String[] args = getExecCommandArgs(); |
| assertTrue(args.length > 0); |
| IConfiguration config = |
| ConfigurationFactory.getInstance().createConfigurationFromArgs(args); |
| verifyConfig(config, cmd, testEnvironment, testResources, workDir); |
| } finally { |
| if (workDir != null) { |
| // Clean up work directory |
| FileUtil.recursiveDelete(workDir); |
| } |
| } |
| } |
| |
| /** Tests an execution of a managed cluster command for a IO exception case. */ |
| @Test |
| public void testExecManagedClusterCommand_ioException() throws Exception { |
| ClusterCommand cmd = createMockManagedCommand(1); |
| File workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId()); |
| mMockClusterClient = Mockito.spy(mMockClusterClient); |
| Mockito.doThrow(new IOException()).when(mMockClusterClient).getTestEnvironment(REQUEST_ID); |
| InvocationEventHandler invocationEventHandler = mScheduler.new InvocationEventHandler(cmd); |
| |
| try { |
| mScheduler.execManagedClusterCommand(cmd, invocationEventHandler); |
| fail("IOException not thrown"); |
| } catch (IOException e) { |
| } |
| assertFalse("work directory was not cleaned up", workDir.exists()); |
| } |
| |
| /** A mock target preparer to test injected TF config objects. */ |
| public static class MockTargetPreparer implements ITargetPreparer { |
| |
| @Option(name = "int", description = "An int value") |
| private int mInt; |
| |
| @Option(name = "string", description = "A string value") |
| private String mString; |
| |
| @Option(name = "list", description = "A list of values") |
| private List<String> mList = new ArrayList<>(); |
| |
| @Option(name = "map", description = "A map of key/value pairs") |
| private Map<String, String> mMap = new TreeMap<>(); |
| |
| public int getInt() { |
| return mInt; |
| } |
| |
| public String getString() { |
| return mString; |
| } |
| |
| public List<String> getList() { |
| return mList; |
| } |
| |
| public Map<String, String> getMap() { |
| return mMap; |
| } |
| } |
| |
| /** Tests an execution of a managed cluster command with addition config objects. */ |
| @Test |
| public void testExecManagedClusterCommand_withTradefedConfigObjects() throws Exception { |
| File workDir = null; |
| try { |
| ClusterCommand cmd = createMockManagedCommand(1); |
| workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId()); |
| TestEnvironment testEnvironment = createMockTestEnvironment(); |
| MultiMap<String, String> optionMap = new MultiMap<>(); |
| optionMap.put("int", "1000"); |
| optionMap.put("string", "foo"); |
| optionMap.put("list", "foo"); |
| optionMap.put("list", "bar"); |
| optionMap.put("list", "zzz"); |
| optionMap.put("map", "foo=bar"); |
| testEnvironment.addTradefedConfigObject( |
| new TradefedConfigObject( |
| TradefedConfigObject.Type.TARGET_PREPARER, |
| MockTargetPreparer.class.getName(), |
| optionMap)); |
| testEnvironment.addTradefedConfigObject( |
| new TradefedConfigObject( |
| TradefedConfigObject.Type.RESULT_REPORTER, |
| ConsoleResultReporter.class.getName(), |
| new MultiMap<>())); |
| List<TestResource> testResources = createMockTestResources(); |
| TestContext testContext = new TestContext(); |
| mMockClusterClient = Mockito.spy(mMockClusterClient); |
| Mockito.doReturn(testEnvironment) |
| .when(mMockClusterClient) |
| .getTestEnvironment(REQUEST_ID); |
| Mockito.doReturn(testResources).when(mMockClusterClient).getTestResources(REQUEST_ID); |
| Mockito.doReturn(testContext) |
| .when(mMockClusterClient) |
| .getTestContext(REQUEST_ID, COMMAND_ID); |
| InvocationEventHandler invocationEventHandler = |
| mScheduler.new InvocationEventHandler(cmd); |
| |
| mScheduler.execManagedClusterCommand(cmd, invocationEventHandler); |
| |
| String[] args = getExecCommandArgs(); |
| assertTrue(args.length > 0); |
| IConfiguration config = |
| ConfigurationFactory.getInstance().createConfigurationFromArgs(args); |
| verifyConfig(config, cmd, testEnvironment, testResources, workDir); |
| |
| // Verify option values. |
| Collection<Object> configObjs = |
| config.getAllConfigurationObjectsOfType( |
| Configuration.TARGET_PREPARER_TYPE_NAME); |
| MockTargetPreparer configObj = |
| (MockTargetPreparer) |
| configObjs.stream() |
| .filter( |
| (obj) -> { |
| return obj instanceof MockTargetPreparer; |
| }) |
| .findFirst() |
| .get(); |
| assertEquals(1000, configObj.getInt()); |
| assertEquals("foo", configObj.getString()); |
| assertEquals(Arrays.asList("foo", "bar", "zzz"), configObj.getList()); |
| assertEquals("bar", configObj.getMap().get("foo")); |
| } finally { |
| // Clean up work directory |
| FileUtil.recursiveDelete(workDir); |
| } |
| } |
| |
| @Test |
| public void testShutdown_stopsHeartbeat() { |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| scheduler.start(); |
| assertFalse(scheduler.getHeartbeatThreadPool().isTerminated()); |
| scheduler.shutdown(); |
| assertTrue(scheduler.getHeartbeatThreadPool().isTerminated()); |
| } |
| |
| @Test |
| public void testShutdownHard_stopsHeartbeat() { |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| scheduler.start(); |
| assertFalse(scheduler.getHeartbeatThreadPool().isTerminated()); |
| scheduler.shutdownHard(); |
| assertTrue(scheduler.getHeartbeatThreadPool().isTerminated()); |
| } |
| |
| /** |
| * Ensure that we do not thrown an exception from scheduling the heartbeat after calling |
| * shutdown on the thread pool. |
| */ |
| @Test |
| public void testShutdownHearbeat() throws Exception { |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| scheduler.getHeartbeatThreadPool().shutdown(); |
| scheduler |
| .getHeartbeatThreadPool() |
| .scheduleAtFixedRate( |
| new Runnable() { |
| @Override |
| public void run() { |
| RunUtil.getDefault().sleep(500); |
| } |
| }, |
| 0, |
| 100, |
| TimeUnit.MILLISECONDS); |
| boolean res = scheduler.getHeartbeatThreadPool().awaitTermination(5, TimeUnit.SECONDS); |
| assertTrue("HeartBeat scheduler did not terminate.", res); |
| } |
| |
| /** Tests whether the checkCommandStatus option is respected. */ |
| @Test |
| public void testCheckCommandState_option() { |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| |
| // create new heartbeat |
| ClusterCommand command = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| InvocationEventHandler handler = scheduler.new InvocationEventHandler(command); |
| Runnable heartbeat = handler.new HeartbeatSender(); |
| |
| // populate invocation context |
| IInvocationContext context = Mockito.mock(IInvocationContext.class, RETURNS_DEEP_STUBS); |
| Mockito.when(context.getInvocationId()).thenReturn("1"); |
| handler.invocationStarted(context); |
| |
| // command status is CANCELED |
| mMockClusterClient = Mockito.mock(IClusterClient.class, RETURNS_DEEP_STUBS); |
| Mockito.when(mMockClusterClient.getCommandStatus(any(), any())) |
| .thenReturn(new ClusterCommandStatus(ClusterCommand.State.CANCELED, "Reason")); |
| |
| // not stopped if check is disabled |
| mMockClusterOptions.setCheckCommandState(false); |
| heartbeat.run(); |
| assertFalse(scheduler.wasStopInvocationCalled()); |
| |
| // stopped if check is enabled |
| mMockClusterOptions.setCheckCommandState(true); |
| heartbeat.run(); |
| assertTrue(scheduler.wasStopInvocationCalled()); |
| |
| Mockito.verify(mMockClusterClient, Mockito.times(1)).getCommandStatus(any(), any()); |
| } |
| |
| /** Tests whether the heartbeat can determine the invocationId to stop. */ |
| @Test |
| public void testCheckCommandState_invocationId() { |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| mMockClusterOptions.setCheckCommandState(true); |
| |
| // create new heartbeat |
| ClusterCommand command = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| InvocationEventHandler handler = scheduler.new InvocationEventHandler(command); |
| Runnable heartbeat = handler.new HeartbeatSender(); |
| |
| // command status is CANCELED |
| mMockClusterClient = Mockito.mock(IClusterClient.class, RETURNS_DEEP_STUBS); |
| Mockito.when(mMockClusterClient.getCommandStatus(any(), any())) |
| .thenReturn(new ClusterCommandStatus(ClusterCommand.State.CANCELED, "Reason")); |
| |
| // not stopped without invocation context |
| handler.setCanceled(false); |
| heartbeat.run(); |
| assertFalse(scheduler.wasStopInvocationCalled()); |
| |
| // not stopped if invocation ID missing |
| IInvocationContext context = Mockito.mock(IInvocationContext.class, RETURNS_DEEP_STUBS); |
| handler.setCanceled(false); |
| handler.invocationStarted(context); |
| heartbeat.run(); |
| assertFalse(scheduler.wasStopInvocationCalled()); |
| |
| // not stopped if invocation ID is non-numeric |
| Mockito.when(context.getInvocationId()).thenReturn("ID"); |
| handler.setCanceled(false); |
| heartbeat.run(); |
| assertFalse(scheduler.wasStopInvocationCalled()); |
| |
| // stopped if invocation ID is numeric |
| Mockito.when(context.getInvocationId()).thenReturn("1"); |
| handler.setCanceled(false); |
| heartbeat.run(); |
| assertTrue(scheduler.wasStopInvocationCalled()); |
| |
| Mockito.verify(mMockClusterClient, Mockito.times(4)).getCommandStatus(any(), any()); |
| } |
| |
| /** Tests whether the heartbeat can determine the cluster command state. */ |
| @Test |
| public void testCheckCommandState_status() throws IOException { |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| mMockClusterOptions.setCheckCommandState(true); |
| |
| // create new heartbeat |
| ClusterCommand command = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); |
| InvocationEventHandler handler = scheduler.new InvocationEventHandler(command); |
| Runnable heartbeat = handler.new HeartbeatSender(); |
| |
| // populate invocation context |
| IInvocationContext context = Mockito.mock(IInvocationContext.class, RETURNS_DEEP_STUBS); |
| Mockito.when(context.getInvocationId()).thenReturn("1"); |
| handler.invocationStarted(context); |
| |
| mMockApiHelper = Mockito.mock(IRestApiHelper.class); |
| |
| // not stopped if status is RUNNING |
| Mockito.when(mMockApiHelper.execute(any(), any(), any(), any())) |
| .thenReturn(buildHttpResponse("{\"state\": \"RUNNING\"}")); |
| heartbeat.run(); |
| assertFalse(scheduler.wasStopInvocationCalled()); |
| |
| // not stopped if status is UNKNOWN |
| Mockito.when(mMockApiHelper.execute(any(), any(), any(), any())) |
| .thenReturn(buildHttpResponse("{\"state\": \"INVALID\"}")); |
| heartbeat.run(); |
| assertFalse(scheduler.wasStopInvocationCalled()); |
| |
| // stopped if status is CANCELED |
| Mockito.when(mMockApiHelper.execute(any(), any(), any(), any())) |
| .thenReturn(buildHttpResponse("{\"state\": \"CANCELED\"}")); |
| heartbeat.run(); |
| assertTrue(scheduler.wasStopInvocationCalled()); |
| |
| Mockito.verify(mMockApiHelper, Mockito.times(3)).execute(any(), any(), any(), any()); |
| } |
| |
| /** Tests upload events with specific host state. */ |
| @Test |
| public void testUploadHostEventWithState() { |
| ArgumentCaptor<ClusterHostEvent> capture = ArgumentCaptor.forClass(ClusterHostEvent.class); |
| |
| // Ignore exceptions here, only test uploading host states. |
| TestableClusterCommandScheduler scheduler = new TestableClusterCommandScheduler(); |
| scheduler.start(); |
| |
| verify(mMockHostUploader).postEvent(capture.capture()); |
| verify(mMockHostUploader).flush(); |
| ClusterHostEvent hostEvent = capture.getValue(); |
| assertNotNull(hostEvent.getHostName()); |
| assertNotNull(hostEvent.getTimestamp()); |
| assertEquals(CommandScheduler.HostState.RUNNING, hostEvent.getHostState()); |
| stopScheduler(scheduler); |
| } |
| |
| @SuppressWarnings("deprecation") |
| private void stopScheduler(TestableClusterCommandScheduler scheduler) { |
| scheduler.stop(); // stop is deprecated. |
| } |
| } |