原来测试代码,可以写得这么简单!!!

| 2009-04-14

写单元测试应该非常简单,

简单到只需要 3、5 行代码,就是一个完整的测试用例,

如果做不到,

那么,9 行代码也就够了。

下面是 goCD 的快速单元测试,即: FastUnitTest

你可以从 https://github.com/gocd/gocd 拷贝一份代码库。

通过执行命令 ./gradlew clean server:FastUnitTest 来运行。

FastUnitTest 一共有 12000 多条 case,在我的笔记本上执行,需要 15 分钟。

其实,里面有一些不是单元测试的测试用例。例如:

  • 数据库结构的升级
  • 外部工具(Git/P4/SVN/HG)的交互

其中,被跳过(Ignored)的测试用例大多数是因为操作系统不匹配,或者测试的前置条件不具备。

下面是 AgentInstanceTest 的代码,用于测试 Agent 对自身状态的检测,例如 Agent 上的磁盘存储空间是否超限。

对,你没有看错,的确是叫“FastUnitTest”,但也的确会访问文件系统。

AgentInstance 的测试用例包括:

文件的详细内容如下

可以看到,每一个测试用例都很简短,且没有很多逻辑计算。

平铺直叙,直接表达测试意图。

/*
 * Copyright 2022 ThoughtWorks, Inc.
 *
 * 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.thoughtworks.go.domain;

public class AgentInstanceTest {
    private SystemEnvironment systemEnvironment;
    public Agent agent;
    private AgentBuildingInfo defaultBuildingInfo;
    private static final String DEFAULT_IP_ADDRESS = "10.18.5.1";
    private AgentStatusChangeListener agentStatusChangeListener;
    private TimeProvider timeProvider;

    @BeforeEach
    void setUp() {
        systemEnvironment = new SystemEnvironment();
        agent = new Agent("uuid2", "CCeDev01", DEFAULT_IP_ADDRESS);
        defaultBuildingInfo = new AgentBuildingInfo("pipeline", "buildLocator");
        agentStatusChangeListener = mock(AgentStatusChangeListener.class);
        timeProvider = mock(TimeProvider.class);
    }

    @AfterEach
    void tearDown() throws Exception {
        FileUtils.deleteQuietly(new File("config/agentkeystore"));
        new SystemEnvironment().setProperty("agent.connection.timeout", "300");
        new SystemEnvironment().clearProperty(SystemEnvironment.AGENT_SIZE_LIMIT);
    }

    @Test
    void shouldReturnBuildLocator() {
        AgentInstance building = building("buildLocator");
        assertThat(building.getBuildLocator()).isEqualTo("buildLocator");
    }

    @Test
    void shouldReturnEmptyStringForNullOperatingSystem() {
        AgentInstance building = AgentInstanceMother.missing();
        assertThat(building.getOperatingSystem()).isEqualTo("");
    }

    @Test
    void shouldReturnHumanReadableUsableSpace() {
        assertThat(AgentInstanceMother.updateUsableSpace(AgentInstanceMother.pending(), 2 * 1024 * 1024 * 1024L).freeDiskSpace().toString()).isEqualTo("2.0 GB");
        assertThat(AgentInstanceMother.updateUsableSpace(AgentInstanceMother.pending(), null).freeDiskSpace().toString()).isEqualTo(DiskSpace.UNKNOWN_DISK_SPACE);
    }

    @Test
    void shouldReturnUnknownUsableSpaceForMissingOrLostContactAgent() {
        assertThat(AgentInstanceMother.missing().freeDiskSpace().toString()).isEqualTo(DiskSpace.UNKNOWN_DISK_SPACE);
        assertThat(AgentInstanceMother.lostContact().freeDiskSpace().toString()).isEqualTo(DiskSpace.UNKNOWN_DISK_SPACE);
    }

    @Test
    void shouldNotifyAgentChangeListenerOnUpdate() {
        AgentInstance idleAgent = AgentInstance.createFromAgent(agent("abc"), new SystemEnvironment(), agentStatusChangeListener);

        idleAgent.update(buildingRuntimeInfo());

        verify(agentStatusChangeListener).onAgentStatusChange(idleAgent);
    }

    @Test
    void shouldNotifyAgentChangeListenerOnAgentBuilding() {
        AgentInstance idleAgent = AgentInstance.createFromAgent(agent("abc"), new SystemEnvironment(), agentStatusChangeListener);

        idleAgent.building(new AgentBuildingInfo("running pipeline/stage/build", "buildLocator"));

        verify(agentStatusChangeListener).onAgentStatusChange(idleAgent);
    }

    @Test
    void shouldNotifyAgentChangeListenerOnCancel() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent("abc"), new SystemEnvironment(), agentStatusChangeListener);

        agentInstance.cancel();

        verify(agentStatusChangeListener).onAgentStatusChange(agentInstance);
    }

    @Test
    void shouldNotifyAgentChangeListenerOnRefreshAndMarkedMissing() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent("abc"), new SystemEnvironment(), agentStatusChangeListener);

        agentInstance.idle();
        agentInstance.refresh();

        assertThat(agentInstance.getStatus()).isEqualTo(AgentStatus.Missing);
        verify(agentStatusChangeListener, times(2)).onAgentStatusChange(agentInstance);
    }

    @Test
    void shouldNotifyAgentChangeListenerOnRefreshAndMarkedLostContact() {
        AgentInstance instance = AgentInstance.createFromAgent(agent, new SystemEnvironment() {
            @Override
            public int getAgentConnectionTimeout() {
                return -1;
            }
        }, agentStatusChangeListener);
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Missing);

        instance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));
        instance.refresh();

        assertThat(instance.getStatus()).isEqualTo(AgentStatus.LostContact);
        verify(agentStatusChangeListener, times(2)).onAgentStatusChange(instance);
    }

    @Test
    void shouldNotifyAgentChangeListenerOnEnablingAgent() {
        AgentInstance instance = AgentInstanceMother.disabled();

        AgentInstance disabledAgent = new AgentInstance(instance.agent, instance.getType(), systemEnvironment, agentStatusChangeListener);

        disabledAgent.enable();

        verify(agentStatusChangeListener).onAgentStatusChange(disabledAgent);
    }

    @Test
    void shouldNotifyAgentChangeListenerOnDisablingAgent() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent("abc"), new SystemEnvironment(), agentStatusChangeListener);

        agentInstance.deny();

        verify(agentStatusChangeListener).onAgentStatusChange(agentInstance);
    }

    @Test
    void shouldNotifyAgentChangeListenerOnConfigSync() {
        AgentInstance instance = AgentInstanceMother.disabled();

        AgentInstance agentInstance = new AgentInstance(instance.agent, instance.getType(), systemEnvironment, agentStatusChangeListener);

        agentInstance.syncAgentFrom(agent);

        verify(agentStatusChangeListener).onAgentStatusChange(agentInstance);
    }

    @Test
    void shouldUpdateAgentBackToIdleAfterCancelledTaskFinishes() {
        AgentInstance cancelledAgentInstance = cancelled();

        AgentRuntimeInfo fromAgent = new AgentRuntimeInfo(cancelledAgentInstance.getAgent().getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        fromAgent.idle();
        cancelledAgentInstance.update(fromAgent);

        assertThat(cancelledAgentInstance.getStatus()).isEqualTo(AgentStatus.Idle);
    }

    @Test
    void shouldClearAllCancelledStateIfAgentSetToIdle() throws InvalidAgentInstructionException {
        Date currentTime = mock(Date.class);
        AgentInstance agentInstance = buildingWithTimeProvider(timeProvider);

        when(timeProvider.currentTime()).thenReturn(currentTime);

        agentInstance.cancel();
        agentInstance.killRunningTasks();

        assertThat(agentInstance.cancelledAt()).isEqualTo(currentTime);

        AgentRuntimeInfo fromAgent = new AgentRuntimeInfo(agentInstance.getAgent().getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        fromAgent.idle();
        agentInstance.update(fromAgent);

        assertThat(agentInstance.getStatus()).isEqualTo(AgentStatus.Idle);
        assertThat(agentInstance.cancelledAt()).isNull();
        assertThat(agentInstance.shouldKillRunningTasks()).isFalse();
    }

    @Test
    void shouldUpdateTheInstallLocation() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        String installPath = "/var/lib/GoServer";
        AgentRuntimeInfo newRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        newRuntimeInfo.setLocation(installPath);
        agentInstance.update(newRuntimeInfo);

        assertThat(agentInstance.getLocation()).isEqualTo(installPath);
    }

    @Test
    void shouldUpdateTheUsableSpace() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));

        AgentRuntimeInfo newRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        newRuntimeInfo.setUsableSpace(1000L);

        assertThat(agentInstance.getUsableSpace()).isNotEqualTo(newRuntimeInfo.getUsableSpace());
        agentInstance.update(newRuntimeInfo);
        assertThat(agentInstance.getUsableSpace()).isEqualTo(newRuntimeInfo.getUsableSpace());
    }

    @Test
    void shouldAssignCertificateToApprovedAgent() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        agentInstance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));

        assertThat(agentInstance.assignCertification()).isTrue();
    }

    @Test
    void shouldNotAssignCertificateToPendingAgent() {
        AgentRuntimeInfo agentRuntimeInfo = AgentRuntimeInfo.fromServer(agent, false, "/var/lib", 0L, "linux");
        AgentInstance agentInstance = createFromLiveAgent(agentRuntimeInfo, systemEnvironment,
                mock(AgentStatusChangeListener.class));

        assertThat(agentInstance.assignCertification()).isFalse();
    }

    @Test
    void shouldInitializeTheLastHeardTimeWhenFirstPing() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        Date time = agentInstance.getLastHeardTime();
        assertThat(time).isNull();
        agentInstance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));
        time = agentInstance.getLastHeardTime();
        assertThat(time).isNotNull();
    }

    @Test
    void shouldUpdateTheLastHeardTime() throws Exception {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        agentInstance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));
        Date time = agentInstance.getLastHeardTime();
        Thread.sleep(1000);
        agentInstance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));
        Date newtime = agentInstance.getLastHeardTime();
        assertThat(newtime.after(time)).isTrue();
    }

    @Test
    void shouldUpdateSupportBuildCommandProtocolFlag() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        agentInstance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));

        agentInstance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));
    }


    @Test
    void shouldUpdateIPForPhysicalMachineWhenUpChanged() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        agentInstance.update(new AgentRuntimeInfo(new AgentIdentifier("ccedev01", "10.18.7.52", "uuid"), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));

        assertThat(agentInstance.getAgent().getIpaddress()).isEqualTo("10.18.7.52");
    }

    @Test
    void shouldCleanBuildingInfoWhenAgentIsIdle() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        agentInstance.update(buildingRuntimeInfo());

        agentInstance.update(idleRuntimeInfo());
        assertThat(agentInstance.getBuildingInfo()).isEqualTo(AgentBuildingInfo.NOT_BUILDING);
    }

    private AgentRuntimeInfo idleRuntimeInfo() {
        AgentRuntimeInfo agentRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        agentRuntimeInfo.idle();
        return agentRuntimeInfo;
    }

    @Test
    void shouldUpdateBuildingInfoWhenAgentIsBuilding() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        AgentRuntimeInfo agentRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        AgentBuildingInfo buildingInfo = new AgentBuildingInfo("running pipeline/stage/build", "buildLocator");
        agentRuntimeInfo.busy(buildingInfo);
        agentInstance.update(agentRuntimeInfo);
        assertThat(agentInstance.getBuildingInfo()).isEqualTo(buildingInfo);
    }

    @Test
    void shouldUpdateBuildingInfoWhenAgentIsBuildingWhenCancelled() {
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        agentInstance.update(buildingRuntimeInfo());

        agentInstance.update(cancelRuntimeInfo());

        assertThat(agentInstance.getBuildingInfo()).isEqualTo(defaultBuildingInfo);
        assertThat(agentInstance.getStatus()).isEqualTo(AgentStatus.Cancelled);
    }

    private AgentRuntimeInfo cancelRuntimeInfo() {
        AgentRuntimeInfo agentRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        agentRuntimeInfo.busy(defaultBuildingInfo);
        agentRuntimeInfo.cancel();
        return agentRuntimeInfo;
    }

    @Test
    void shouldNotChangePendingAgentIpAddress() {
        AgentInstance pending = createFromLiveAgent(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"),
                systemEnvironment, mock(AgentStatusChangeListener.class));
        AgentRuntimeInfo info = new AgentRuntimeInfo(new AgentIdentifier("ccedev01", "10.18.7.52", "uuid"), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        assertThat(pending.isIpChangeRequired(info.getIpAdress())).isFalse();
    }

    @Test
    void shouldChangeIpWhenSameAgentIpChanged() {
        AgentInstance instance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        AgentRuntimeInfo info = new AgentRuntimeInfo(new AgentIdentifier("ccedev01", "10.18.7.52", "uuid"), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        assertThat(instance.isIpChangeRequired(info.getIpAdress())).isTrue();
    }

    @Test
    void shouldNotChangeIpWhenIpNotChanged() {
        AgentInstance instance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        assertThat(instance.isIpChangeRequired(DEFAULT_IP_ADDRESS)).isFalse();
    }

    @Test
    void shouldDefaultToMissingStatusWhenSyncAnApprovedAgent() {
        AgentInstance instance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        instance.syncAgentFrom(agent);
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Missing);
    }

    @Test
    void pendingAgentshouldNotBeRegistered() {
        AgentRuntimeInfo agentRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        AgentInstance instance = createFromLiveAgent(agentRuntimeInfo, systemEnvironment,
                mock(AgentStatusChangeListener.class));
        assertThat(instance.isRegistered()).isFalse();
    }

    @Test
    void deniedAgentshouldBeRegistered() {
        agent.disable();
        AgentInstance instance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));

        assertThat(instance.isRegistered()).isTrue();
    }

    @Test
    void shouldBeRegisteredForIdleAgent() {
        AgentInstance instance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        instance.update(idleRuntimeInfo());
        assertThat(instance.isRegistered()).isTrue();
    }

    @Test
    void shouldBecomeIdleAfterApprove() {
        AgentInstance instance = createFromLiveAgent(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"),
                systemEnvironment, mock(AgentStatusChangeListener.class));
        instance.enable();
        instance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Idle);
    }

    @Test
    void shouldBeMissingWhenNeverHeardFromAnyAgent() {
        AgentInstance instance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Missing);

        instance.refresh();
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Missing);
    }

    @Test
    void shouldBeLostContactWhenLastHeardTimeExeedTimeOut() {
        AgentInstance instance = AgentInstance.createFromAgent(agent, new SystemEnvironment() {
            @Override
            public int getAgentConnectionTimeout() {
                return -1;
            }
        }, mock(AgentStatusChangeListener.class));
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Missing);

        instance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));
        instance.refresh();
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.LostContact);
    }

    @Test
    void shouldRefreshDisabledAgent() {
        agent.disable();
        AgentInstance instance = AgentInstance.createFromAgent(agent, new SystemEnvironment() {
            @Override
            public int getAgentConnectionTimeout() {
                return -1;
            }
        }, mock(AgentStatusChangeListener.class));
        instance.update(new AgentRuntimeInfo(agent.getAgentIdentifier(), Building, currentWorkingDirectory(), "cookie"));

        instance.refresh();

        assertThat(instance.getRuntimeStatus()).isEqualTo(AgentRuntimeStatus.LostContact);
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Disabled);
    }

    @Test
    void shouldDenyPendingAgent() {
        AgentRuntimeInfo agentRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        AgentInstance instance = createFromLiveAgent(agentRuntimeInfo, systemEnvironment,
                mock(AgentStatusChangeListener.class));
        instance.deny();

        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Disabled);
    }

    @Test
    void shouldBeLiveStatus() {
        AgentInstance instance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        instance.update(idleRuntimeInfo());
        instance.refresh();
        assertThat(instance.getStatus()).isEqualTo(AgentStatus.Idle);
    }

    @Test
    void shouldSyncIPWithConfig() {
        AgentInstance originalAgentInstance = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));

        originalAgentInstance.update(new AgentRuntimeInfo(new AgentIdentifier("CCeDev01", "10.18.5.2", "uuid2"), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie"));

        assertThat(originalAgentInstance.getAgent()).isEqualTo(new Agent("uuid2", "CCeDev01", "10.18.5.2"));
    }

    @Test
    void shouldKeepOriginalStatusWhenAgentIsNotDenied() {
        AgentInstance original = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        original.update(buildingRuntimeInfo(agent));

        original.syncAgentFrom(agent);
        assertThat(original.getStatus()).isEqualTo(AgentStatus.Building);
    }

    @Test
    void shouldDenyAgentWhenAgentIsDeniedInConfigFile() {
        AgentInstance original = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        original.update(buildingRuntimeInfo());

        Agent newAgent = new Agent(agent.getUuid(), agent.getHostname(), agent.getIpaddress());
        newAgent.disable();

        original.syncAgentFrom(newAgent);
        assertThat(original.getStatus()).isEqualTo(AgentStatus.Disabled);
    }

    @Test
    void shouldDenyAgentWhenItIsNotBuilding() {
        AgentInstance original = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        original.update(idleRuntimeInfo());

        original.deny();
        assertThat(agent.isDisabled()).isTrue();
        assertThat(original.getStatus()).isEqualTo(AgentStatus.Disabled);
    }

    @Test
    void shouldReturnFalseWhenAgentHasEnoughSpace() {
        AgentInstance original = AgentInstance.createFromAgent(agent, new SystemEnvironment() {
            @Override
            public long getAgentSizeLimit() {
                return 100 * 1024 * 1024;
            }
        }, mock(AgentStatusChangeListener.class));
        AgentRuntimeInfo newRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        long is110M = 110 * 1024 * 1024;
        newRuntimeInfo.setUsableSpace(is110M);
        original.update(newRuntimeInfo);

        assertThat(original.isLowDiskSpace()).isFalse();
    }

    @Test
    void shouldReturnTrueWhenFreeDiskOnAgentIsLow() {
        AgentInstance original = AgentInstance.createFromAgent(agent, new SystemEnvironment() {
            @Override
            public long getAgentSizeLimit() {
                return 100 * 1024 * 1024;
            }
        }, mock(AgentStatusChangeListener.class));
        AgentRuntimeInfo newRuntimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        long is90M = 90 * 1024 * 1024;
        newRuntimeInfo.setUsableSpace(is90M);
        original.update(newRuntimeInfo);

        assertThat(original.isLowDiskSpace()).isTrue();
    }

    @Test
    void shouldBeAbleToDenyAgentWhenItIsBuilding() {
        AgentInstance original = AgentInstance.createFromAgent(agent, systemEnvironment, mock(AgentStatusChangeListener.class));
        AgentRuntimeInfo runtimeInfo = buildingRuntimeInfo();
        original.update(runtimeInfo);
        assertThat(original.canDisable()).isTrue();
        original.deny();
        assertThat(agent.isDisabled()).isTrue();
        assertThat(original.getStatus()).isEqualTo(AgentStatus.Disabled);
        assertThat(original.getBuildingInfo()).isEqualTo(runtimeInfo.getBuildingInfo());
    }

    @Test
    void shouldOrderByHostname() {
        AgentInstance agentA = new AgentInstance(new Agent("UUID", "A", "127.0.0.1"), LOCAL, systemEnvironment, null);
        AgentInstance agentB = new AgentInstance(new Agent("UUID", "B", "127.0.0.2"), LOCAL, systemEnvironment, null);

        assertThat(agentA.compareTo(agentA)).isEqualTo(0);
        assertThat(agentA.compareTo(agentB)).isLessThan(0);
        assertThat(agentB.compareTo(agentA)).isGreaterThan(0);
    }

    @Test
    void shouldNotBeEqualIfUuidIsNotEqual() {
        AgentInstance agentA = new AgentInstance(new Agent("UUID", "A", "127.0.0.1"), LOCAL, systemEnvironment, null);
        AgentInstance copyOfAgentA = new AgentInstance(new Agent("UUID", "A", "127.0.0.1"),
                LOCAL, systemEnvironment, null);
        AgentInstance agentB = new AgentInstance(new Agent("UUID", "B", "127.0.0.2"), LOCAL, systemEnvironment, null);

        assertThat(agentA).isNotEqualTo(agentB);
        assertThat(agentB).isNotEqualTo(agentA);
        assertThat(agentA).isEqualTo(copyOfAgentA);
    }

    @Test
    void shouldBeAbleToDenyAgentThatIsRunningCancelledJob() {
        Agent agent = new Agent("UUID", "A", "127.0.0.1");
        AgentInstance agentInstance = new AgentInstance(agent, LOCAL, systemEnvironment, mock(AgentStatusChangeListener.class));
        agentInstance.cancel();
        AgentBuildingInfo cancelled = agentInstance.getBuildingInfo();
        assertThat(agentInstance.canDisable()).isTrue();
        agentInstance.deny();
        assertThat(agent.isDisabled()).isTrue();
        assertThat(agentInstance.getStatus()).isEqualTo(AgentStatus.Disabled);
        assertThat(agentInstance.getBuildingInfo()).isEqualTo(cancelled);
    }

    @Test
    void shouldReturnNullWhenNoMatchingJobs() {
        AgentInstance agentInstance = new AgentInstance(agent("linux, mercurial"), LOCAL, systemEnvironment, null);

        JobPlan matchingJob = agentInstance.firstMatching(new ArrayList<>());
        assertThat(matchingJob).isNull();
    }

    @Test
    void shouldReturnFirstMatchingJobPlan() {
        AgentInstance agentInstance = new AgentInstance(agent("linux, mercurial"), LOCAL, systemEnvironment, null);

        List<JobPlan> plans = jobPlans("linux, svn", "linux, mercurial");
        JobPlan matchingJob = agentInstance.firstMatching(plans);
        assertThat(matchingJob).isEqualTo(plans.get(1));
    }

    @Test
    void shouldReturnAJobPlanWithMatchingUuidSet() {
        Agent agent = agent("linux, mercurial");
        AgentInstance agentInstance = new AgentInstance(agent, LOCAL, systemEnvironment, null);

        final JobPlan job = jobPlan("pipeline-name", "job-name", "resource", agent.getUuid());
        JobPlan matchingJob = agentInstance.firstMatching(new ArrayList<JobPlan>() {{
            add(job);
        }});
        assertThat(matchingJob).isEqualTo(job);
    }

    @Test
    void shouldNotReturnAJobWithMismatchedUuid() {
        Agent agent = agent("linux, mercurial");
        AgentInstance agentInstance = new AgentInstance(agent, LOCAL, systemEnvironment, null);

        final JobPlan job = jobPlan("pipeline-name", "job-name", "linux", agent.getUuid() + "-ensure-doesn't-match");
        JobPlan matchingJob = agentInstance.firstMatching(new ArrayList<JobPlan>() {{
            add(job);
        }});
        assertThat(matchingJob).isNull();
    }

    @Test
    void shouldSetAgentToIdleWhenItIsApproved() {
        AgentInstance pendingAgentInstance = AgentInstanceMother.pending();
        Agent agent = new Agent(pendingAgentInstance.getUuid(), pendingAgentInstance.getHostname(), pendingAgentInstance.getIpAddress());
        pendingAgentInstance.syncAgentFrom(agent);
        AgentStatus status = pendingAgentInstance.getStatus();
        assertThat(status).isEqualTo(AgentStatus.Idle);
    }

    @Test
    void syncConfigShouldUpdateElasticAgentRuntimeInfo() {
        AgentInstance agentInstance = AgentInstanceMother.idle();

        Agent agent = new Agent(agentInstance.getUuid(), agentInstance.getHostname(), agentInstance.getIpAddress());
        agent.setElasticAgentId("i-123456");
        agent.setElasticPluginId("com.example.aws");

        assertThat(agentInstance.isElastic()).isFalse();
        agentInstance.syncAgentFrom(agent);
        assertThat(agentInstance.isElastic()).isTrue();

        assertThat(agentInstance.elasticAgentMetadata().elasticAgentId()).isEqualTo("i-123456");
        assertThat(agentInstance.elasticAgentMetadata().elasticPluginId()).isEqualTo("com.example.aws");
    }

    @Test
    void shouldReturnFreeDiskSpace() {
        assertThat(AgentInstanceMother.updateRuntimeStatus(AgentInstanceMother.updateUsableSpace(AgentInstanceMother.idle(new Date(), "CCeDev01"), 1024L), AgentRuntimeStatus.Missing).freeDiskSpace()).isEqualTo(DiskSpace.unknownDiskSpace());
        assertThat(AgentInstanceMother.updateRuntimeStatus(AgentInstanceMother.updateUsableSpace(AgentInstanceMother.idle(new Date(), "CCeDev01"), 1024L), AgentRuntimeStatus.LostContact).freeDiskSpace()).isEqualTo(DiskSpace.unknownDiskSpace());
        assertThat(AgentInstanceMother.updateRuntimeStatus(AgentInstanceMother.updateUsableSpace(AgentInstanceMother.idle(new Date(), "CCeDev01"), 1024L), AgentRuntimeStatus.Idle).freeDiskSpace()).isEqualTo(new DiskSpace(1024L));
        assertThat(AgentInstanceMother.updateRuntimeStatus(AgentInstanceMother.updateUsableSpace(AgentInstanceMother.idle(new Date(), "CCeDev01"), null), AgentRuntimeStatus.Idle).freeDiskSpace()).isEqualTo(DiskSpace.unknownDiskSpace());

    }

    @Test
    void shouldReturnAppropriateMissingStatus() {
        AgentInstance missing = AgentInstanceMother.missing();
        assertThat(missing.isMissing()).isTrue();
        AgentInstance building = building();
        assertThat(building.isMissing()).isFalse();
    }

    @Test
    void shouldNotMatchJobPlanIfJobRequiresElasticAgent_MatchingIsManagedByBuildAssignmentService() {
        Agent agent = new Agent("uuid");
        agent.setElasticAgentId("elastic-agent-id-1");
        String elasticPluginId = "elastic-plugin-id-1";
        agent.setElasticPluginId(elasticPluginId);
        AgentInstance agentInstance = new AgentInstance(agent, REMOTE, mock(SystemEnvironment.class), null);
        DefaultJobPlan jobPlan1 = new DefaultJobPlan();
        jobPlan1.setElasticProfile(new ElasticProfile("foo", "prod-cluster"));
        List<JobPlan> jobPlans = asList(jobPlan1, new DefaultJobPlan());

        assertThat(agentInstance.firstMatching(jobPlans)).isNull();
    }

    @Test
    void shouldNotMatchJobPlanIfTheAgentWasLaunchedByADifferentPluginFromThatConfiguredForTheJob() {
        Agent agent = new Agent("uuid");
        agent.setElasticAgentId("elastic-agent-id-1");
        String elasticPluginId = "elastic-plugin-id-1";
        agent.setElasticPluginId(elasticPluginId);
        AgentInstance agentInstance = new AgentInstance(agent, REMOTE, mock(SystemEnvironment.class), null);
        DefaultJobPlan jobPlan1 = new DefaultJobPlan();
        jobPlan1.setElasticProfile(new ElasticProfile("foo", "prod-cluster"));
        List<JobPlan> jobPlans = asList(jobPlan1, new DefaultJobPlan());

        assertThat(agentInstance.firstMatching(jobPlans)).isNull();
    }

    @Test
    void shouldNotMatchJobPlanIfTheAgentIsElasticAndJobHasResourcesDefined() {
        Agent agent = new Agent("uuid", "hostname", "11.1.1.1", singletonList("r1"));
        agent.setElasticAgentId("elastic-agent-id-1");
        String elasticPluginId = "elastic-plugin-id-1";
        agent.setElasticPluginId(elasticPluginId);
        AgentInstance agentInstance = new AgentInstance(agent, REMOTE, mock(SystemEnvironment.class), null);
        DefaultJobPlan jobPlan1 = new DefaultJobPlan();
        jobPlan1.setResources(asList(new Resource("r1")));
        List<JobPlan> jobPlans = asList(jobPlan1, new DefaultJobPlan());

        assertThat(agentInstance.firstMatching(jobPlans)).isNull();
    }

    @Test
    void lostContact() {
        AgentInstance agentInstance = building();
        agentInstance.lostContact();
        assertThat(agentInstance.getStatus()).isEqualTo(AgentStatus.LostContact);

        AgentInstance pendingInstance = AgentInstanceMother.pending();
        pendingInstance.lostContact();
        assertThat(pendingInstance.getStatus()).isEqualTo(AgentStatus.Pending);

        AgentInstance disabledInstance = AgentInstanceMother.disabled();
        disabledInstance.lostContact();
        assertThat(disabledInstance.getStatus()).isEqualTo(AgentStatus.Disabled);
    }

    @Test
    void shouldNotRefreshWhenAgentStatusIsPending() {
        AgentInstance agentInstance = AgentInstanceMother.pendingInstance();
        agentInstance.refresh();
        assertThat(agentInstance.getStatus()).isEqualTo(AgentStatus.Pending);
    }

    @Test
    void shouldMarkAgentAsMissingWhenLastHeardTimeIsNull() {
        Agent agent = new Agent("1234", "localhost", "192.168.0.1");
        AgentInstance agentInstance = AgentInstance.createFromAgent(agent, new SystemEnvironment(), mock(AgentStatusChangeListener.class));
        agentInstance.refresh();
        assertThat(agentInstance.getRuntimeStatus()).isEqualTo(AgentRuntimeStatus.Missing);
        assertThat(agentInstance.getLastHeardTime()).isNotNull();
    }

    @Test
    void shouldNotRefreshAgentStateWhenAgentIsMissingAndLostContactDurationHasNotExceeded() {
        AgentInstance agentInstance = AgentInstanceMother.missing();
        agentInstance.refresh();
        assertThat(agentInstance.getRuntimeStatus()).isEqualTo(AgentRuntimeStatus.Missing);
    }

    @Test
    void shouldChangeAgentStatusToLostContactWhenLostAgentTimeoutHasExceeded() throws IllegalAccessException {
        AgentInstance agentInstance = AgentInstanceMother.missing();
        int agentConnectionTimeoutInMillis = systemEnvironment.getAgentConnectionTimeout() * 1000;
        Date timeLoggedForMissingStatus = new Date(new Date().getTime() - agentConnectionTimeoutInMillis);
        FieldUtils.writeField(agentInstance, "lastHeardTime", timeLoggedForMissingStatus, true);
        agentInstance.refresh();
        assertThat(agentInstance.getRuntimeStatus()).isEqualTo(AgentRuntimeStatus.LostContact);
    }

    @Test
    void buildingMethodShouldUpdateAgentRuntimeStatusToBuildingAndSetSpecifiedBuildInfoInItsRuntimeInfo(){
        Agent mockAgent = mock(Agent.class);
        AgentRuntimeInfo mockAgentRuntimeInfo = mock(AgentRuntimeInfo.class);
        AgentBuildingInfo mockAgentBuildingInfo = mock(AgentBuildingInfo.class);
        SystemEnvironment mockSysEnv = mock(SystemEnvironment.class);
        AgentStatusChangeListener mockAgentStatusChangeListener = mock(AgentStatusChangeListener.class);

        when(mockAgent.isFromLocalHost()).thenReturn(true);
        when(mockAgentRuntimeInfo.agent()).thenReturn(mockAgent);
        when(mockSysEnv.isAutoRegisterLocalAgentEnabled()).thenReturn(true);
        when(mockAgentRuntimeInfo.isCancelled()).thenReturn(false);
        doNothing().when(mockAgentRuntimeInfo).busy(mockAgentBuildingInfo);

        AgentInstance agentInstance = createFromLiveAgent(mockAgentRuntimeInfo, mockSysEnv, mockAgentStatusChangeListener);
        agentInstance.building(mockAgentBuildingInfo);

        verify(mockAgentRuntimeInfo, atLeastOnce()).getRuntimeStatus();
        verify(mockAgentRuntimeInfo).setRuntimeStatus(Building);
        verify(mockAgentRuntimeInfo).busy(mockAgentBuildingInfo);
        verify(mockAgentStatusChangeListener).onAgentStatusChange(agentInstance);
    }

    @Test
    void shouldGetAgentAndBootstrapperVersions() {
        AgentInstance agentInstance = AgentInstanceMother.idle();

        AgentRuntimeInfo newRuntimeInfo = new AgentRuntimeInfo(agentInstance.getAgentIdentifier(), agentInstance.getRuntimeStatus(), "some-location", "some-cookie");
        newRuntimeInfo.updateBootstrapperVersion("20.3.0-1234").updateAgentVersion("20.5.0-2345");

        agentInstance.update(newRuntimeInfo);

        assertThat(agentInstance.getAgentBootstrapperVersion()).isEqualTo("20.3.0-1234");
        assertThat(agentInstance.getAgentVersion()).isEqualTo("20.5.0-2345");
    }

    @Nested
    class Matches {
        @Test
        void shouldReturnTrueIfMatchesTheFilter() {
            AgentInstance pending = AgentInstanceMother.pending();
            assertThat(pending.matches(Pending)).isTrue();

            Agent pendingAgent = pending.getAgent();
            pendingAgent.setElasticAgentId("elastic-agent-id");
            pendingAgent.setElasticPluginId("elastic-plugin-id");

            pending.syncAgentFrom(pendingAgent);

            assertThat(pending.matches(Elastic)).isTrue();

            AgentInstance nullInstance = AgentInstanceMother.nullInstance();
            assertThat(nullInstance.matches(Null)).isTrue();
        }

        @Test
        void shouldReturnFalseIfDoesNotMatchTheFilter() {
            AgentInstance building = building();
            assertThat(building.matches(Pending)).isFalse();
            assertThat(building.matches(Elastic)).isFalse();

            AgentInstance pending = AgentInstanceMother.pending();
            assertThat(pending.matches(Elastic)).isFalse();
            assertThat(pending.matches(Null)).isFalse();

            AgentInstance idle = AgentInstanceMother.idle();
            Agent idleAgent = idle.getAgent();
            idleAgent.setElasticAgentId("elastic-agent-id");
            idleAgent.setElasticPluginId("elastic-plugin-id");
            idle.syncAgentFrom(idleAgent);
            assertThat(idle.matches(Pending)).isFalse();
            assertThat(idle.matches(Null)).isFalse();
        }
    }

    @Nested
    class killRunningTasks {
        @Test
        void shouldAddAKillRunningTasksInstructionToAgent() throws InvalidAgentInstructionException {
            AgentInstance agentInstance = cancelled();

            agentInstance.killRunningTasks();

            assertThat(agentInstance.shouldKillRunningTasks()).isTrue();
        }

        @Test
        void shouldErrorOutIfAddingKillRunningTasksInstructionIfAgentRuntimeStatusIsNotCancelled() {
            AgentInstance agentInstance = building();

            assertThatExceptionOfType(InvalidAgentInstructionException.class)
                    .isThrownBy(() -> agentInstance.killRunningTasks())
                    .withMessage("The agent should be in cancelled state before attempting to kill running tasks. Current Agent state is: 'Building'");
        }

        @Test
        void shouldErrorOutIfMakingMultipleRequestsToKillRunningTasks() throws InvalidAgentInstructionException {
            AgentInstance agentInstance = cancelled();

            agentInstance.killRunningTasks();

            assertThatExceptionOfType(InvalidAgentInstructionException.class)
                    .isThrownBy(() -> agentInstance.killRunningTasks())
                    .withMessage("There is a pending request to kill running task.");
        }
    }

    @Nested
    class cancel {
        @Test
        void shouldKeepStatusAsCancelled() {
            AgentInstance buildingAgentInstance = building("buildLocator");

            buildingAgentInstance.cancel();

            buildingAgentInstance.update(buildingRuntimeInfo(buildingAgentInstance.getAgent()));

            assertThat(buildingAgentInstance.getStatus()).isEqualTo(AgentStatus.Cancelled);
        }

        @Test
        void shouldRecordTheTimeWhenAgentIsMovedToCancelState() {
            Date currentTime = mock(Date.class);
            TimeProvider timeProvider = mock(TimeProvider.class);
            AgentInstance agentInstance = buildingWithTimeProvider(timeProvider);

            when(timeProvider.currentTime()).thenReturn(currentTime);

            agentInstance.cancel();

            assertThat(agentInstance.cancelledAt()).isEqualTo(currentTime);
        }
    }

    @Nested
    class agentInstruction {
        @Test
        void shouldBeToDoNothingIfJobNotCancelled() {
            AgentInstance agentInstance = building("buildLocator");

            assertThat(agentInstance.agentInstruction()).isEqualTo(NONE);
        }

        @Test
        void shouldBeToCancelIfJobCancelled() {
            AgentInstance agentInstance = building();

            agentInstance.cancel();

            assertThat(agentInstance.agentInstruction()).isEqualTo(CANCEL);
        }

        @Test
        void shouldBeToKillRunningTasks() throws InvalidAgentInstructionException {
            AgentInstance agentInstance = building();

            agentInstance.cancel();
            agentInstance.killRunningTasks();

            assertThat(agentInstance.agentInstruction()).isEqualTo(KILL_RUNNING_TASKS);
        }
    }

    @Nested
    class isStuckInCancel {
        @Test
        void shouldBeTrueIfIsInCancelledStateForMoreThan10Mins() {
            Date currentTime = new Date();
            Date after10Mins = new Date(currentTime.getTime() + 600001);
            TimeProvider timeProvider = mock(TimeProvider.class);
            AgentInstance agentInstance = buildingWithTimeProvider(timeProvider);

            when(timeProvider.currentTime()).thenReturn(currentTime, after10Mins);

            agentInstance.cancel();

            assertThat(agentInstance.isStuckInCancel()).isTrue();
        }

        @Test
        void shouldNotBeTrueForAgentsWhichAreNotCancelled() {
            AgentInstance agentInstance = buildingWithTimeProvider(timeProvider);

            assertThat(agentInstance.isStuckInCancel()).isFalse();
        }
    }

    private List<JobPlan> jobPlans(String... resources) {
        ArrayList<JobPlan> plans = new ArrayList<>();
        int count = 1;
        for (String resource : resources) {
            plans.add(jobPlan("pipeline" + count, "job" + count, resource, null));
            count++;
        }
        return plans;
    }

    private DefaultJobPlan jobPlan(String pipelineName, String jobName, String resource, String uuid) {
        JobIdentifier jobIdentifier = new JobIdentifier(pipelineName, 1, "1", "stage1", "1", jobName, 1L);
        DefaultJobPlan plan = new DefaultJobPlan(new Resources(resource), new ArrayList<>(), 100, jobIdentifier, null, new EnvironmentVariables(), new EnvironmentVariables(), null, null);
        plan.setAgentUuid(uuid);
        return plan;
    }

    private Agent agent(String resources) {
        return new Agent("UUID", "A", "127.0.0.1", commaSeparatedStrToList(resources));
    }

    private AgentRuntimeInfo buildingRuntimeInfo() {
        return buildingRuntimeInfo(agent);
    }

    private AgentRuntimeInfo buildingRuntimeInfo(Agent agent) {
        AgentRuntimeInfo runtimeInfo = new AgentRuntimeInfo(agent.getAgentIdentifier(), AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie");
        runtimeInfo.busy(defaultBuildingInfo);
        return runtimeInfo;
    }

    public static AgentInstance buildingWithTimeProvider(TimeProvider timeProvider) {
        Agent idleAgentConfig = new Agent("uuid2", "localhost", "10.18.5.1");
        AgentRuntimeInfo agentRuntimeInfo = new AgentRuntimeInfo(idleAgentConfig.getAgentIdentifier(), Building, currentWorkingDirectory(), "cookie");
        agentRuntimeInfo.setLocation("/var/lib/foo");
        agentRuntimeInfo.idle();
        agentRuntimeInfo.setUsableSpace(10 * 1024l);
        AgentInstance agentInstance = new AgentInstance(idleAgentConfig, LOCAL, new SystemEnvironment(), mock(AgentStatusChangeListener.class), timeProvider);
        agentInstance.enable();
        return agentInstance;
    }
}