11import { Octokit } from '@octokit/rest' ;
22import { mocked } from 'jest-mock' ;
3+ import moment from 'moment-timezone' ;
34import nock from 'nock' ;
45
56import { listEC2Runners } from '../aws/runners' ;
67import * as ghAuth from '../gh-auth/gh-auth' ;
7- import * as scale from '../scale-runners/scale-up' ;
8+ import { createRunners } from '../scale-runners/scale-up' ;
89import { adjust } from './pool' ;
910
1011const mockOctokit = {
@@ -24,6 +25,7 @@ jest.mock('@octokit/rest', () => ({
2425
2526jest . mock ( './../aws/runners' ) ;
2627jest . mock ( './../gh-auth/gh-auth' ) ;
28+ jest . mock ( './../scale-runners/scale-up' ) ;
2729
2830const mocktokit = Octokit as jest . MockedClass < typeof Octokit > ;
2931const mockedAppAuth = mocked ( ghAuth . createGithubAppAuth , {
@@ -36,6 +38,36 @@ const mockListRunners = mocked(listEC2Runners);
3638const cleanEnv = process . env ;
3739
3840const ORG = 'my-org' ;
41+ const MINIMUM_TIME_RUNNING = 15 ;
42+
43+ const ec2InstancesRegistered = [
44+ {
45+ instanceId : 'i-1-idle' ,
46+ launchTime : new Date ( ) ,
47+ type : 'Org' ,
48+ owner : ORG ,
49+ } ,
50+ {
51+ instanceId : 'i-2-busy' ,
52+ launchTime : new Date ( ) ,
53+ type : 'Org' ,
54+ owner : ORG ,
55+ } ,
56+ {
57+ instanceId : 'i-3-offline' ,
58+ launchTime : new Date ( ) ,
59+ type : 'Org' ,
60+ owner : ORG ,
61+ } ,
62+ {
63+ instanceId : 'i-4-idle-older-than-minimum-time-running' ,
64+ launchTime : moment ( new Date ( ) )
65+ . subtract ( MINIMUM_TIME_RUNNING + 3 , 'minutes' )
66+ . toDate ( ) ,
67+ type : 'Org' ,
68+ owner : ORG ,
69+ } ,
70+ ] ;
3971
4072beforeEach ( ( ) => {
4173 nock . disableNetConnect ( ) ;
@@ -55,6 +87,7 @@ beforeEach(() => {
5587 process . env . INSTANCE_TYPES = 'm5.large' ;
5688 process . env . INSTANCE_TARGET_CAPACITY_TYPE = 'spot' ;
5789 process . env . RUNNER_OWNER = ORG ;
90+ process . env . RUNNER_BOOT_TIME_IN_MINUTES = MINIMUM_TIME_RUNNING . toString ( ) ;
5891
5992 const mockTokenReturnValue = {
6093 data : {
@@ -66,66 +99,39 @@ beforeEach(() => {
6699 mockOctokit . paginate . mockImplementation ( ( ) => [
67100 {
68101 id : 1 ,
69- name : 'i-1' ,
102+ name : 'i-1-idle ' ,
70103 os : 'linux' ,
71104 status : 'online' ,
72105 busy : false ,
73106 labels : [ ] ,
74107 } ,
75108 {
76109 id : 2 ,
77- name : 'i-2' ,
110+ name : 'i-2-busy ' ,
78111 os : 'linux' ,
79112 status : 'online' ,
80113 busy : true ,
81114 labels : [ ] ,
82115 } ,
83116 {
84117 id : 3 ,
85- name : 'i-3' ,
118+ name : 'i-3-offline ' ,
86119 os : 'linux' ,
87120 status : 'offline' ,
88121 busy : false ,
89122 labels : [ ] ,
90123 } ,
91124 {
92- id : 11 ,
93- name : 'j-1' , // some runner of another env
125+ id : 3 ,
126+ name : 'i-4-idle-older-than-minimum-time-running' ,
94127 os : 'linux' ,
95128 status : 'online' ,
96129 busy : false ,
97130 labels : [ ] ,
98131 } ,
99- {
100- id : 12 ,
101- name : 'j-2' , // some runner of another env
102- os : 'linux' ,
103- status : 'online' ,
104- busy : true ,
105- labels : [ ] ,
106- } ,
107132 ] ) ;
108133
109- mockListRunners . mockImplementation ( async ( ) => [
110- {
111- instanceId : 'i-1' ,
112- launchTime : new Date ( ) ,
113- type : 'Org' ,
114- owner : ORG ,
115- } ,
116- {
117- instanceId : 'i-2' ,
118- launchTime : new Date ( ) ,
119- type : 'Org' ,
120- owner : ORG ,
121- } ,
122- {
123- instanceId : 'i-3' ,
124- launchTime : new Date ( ) ,
125- type : 'Org' ,
126- owner : ORG ,
127- } ,
128- ] ) ;
134+ mockListRunners . mockImplementation ( async ( ) => ec2InstancesRegistered ) ;
129135
130136 const mockInstallationIdReturnValueOrgs = {
131137 data : {
@@ -156,16 +162,74 @@ beforeEach(() => {
156162
157163describe ( 'Test simple pool.' , ( ) => {
158164 describe ( 'With GitHub Cloud' , ( ) => {
159- it ( 'Top up pool with pool size 2.' , async ( ) => {
160- const spy = jest . spyOn ( scale , 'createRunners' ) ;
161- await expect ( adjust ( { poolSize : 2 } ) ) . resolves ;
162- expect ( spy ) . toBeCalled ;
165+ it ( 'Top up pool with pool size 2 registered.' , async ( ) => {
166+ await expect ( await adjust ( { poolSize : 3 } ) ) . resolves ;
167+ expect ( createRunners ) . toHaveBeenCalledTimes ( 1 ) ;
168+ expect ( createRunners ) . toHaveBeenCalledWith (
169+ expect . anything ( ) ,
170+ expect . objectContaining ( { numberOfRunners : 1 } ) ,
171+ expect . anything ( ) ,
172+ ) ;
163173 } ) ;
164174
165175 it ( 'Should not top up if pool size is reached.' , async ( ) => {
166- const spy = jest . spyOn ( scale , 'createRunners' ) ;
167- await expect ( adjust ( { poolSize : 1 } ) ) . resolves ;
168- expect ( spy ) . not . toHaveBeenCalled ;
176+ await expect ( await adjust ( { poolSize : 1 } ) ) . resolves ;
177+ expect ( createRunners ) . not . toHaveBeenCalled ( ) ;
178+ } ) ;
179+
180+ it ( 'Should top up if pool size is not reached including a booting instance.' , async ( ) => {
181+ mockListRunners . mockImplementation ( async ( ) => [
182+ ...ec2InstancesRegistered ,
183+ {
184+ instanceId : 'i-4-still-booting' ,
185+ launchTime : moment ( new Date ( ) )
186+ . subtract ( MINIMUM_TIME_RUNNING - 3 , 'minutes' )
187+ . toDate ( ) ,
188+ type : 'Org' ,
189+ owner : ORG ,
190+ } ,
191+ {
192+ instanceId : 'i-5-orphan' ,
193+ launchTime : moment ( new Date ( ) )
194+ . subtract ( MINIMUM_TIME_RUNNING + 3 , 'minutes' )
195+ . toDate ( ) ,
196+ type : 'Org' ,
197+ owner : ORG ,
198+ } ,
199+ ] ) ;
200+
201+ // 2 idle + 1 booting = 3, top up with 2 to match a pool of 5
202+ await expect ( await adjust ( { poolSize : 5 } ) ) . resolves ;
203+ expect ( createRunners ) . toHaveBeenCalledWith (
204+ expect . anything ( ) ,
205+ expect . objectContaining ( { numberOfRunners : 2 } ) ,
206+ expect . anything ( ) ,
207+ ) ;
208+ } ) ;
209+
210+ it ( 'Should not top up if pool size is reached including a booting instance.' , async ( ) => {
211+ mockListRunners . mockImplementation ( async ( ) => [
212+ ...ec2InstancesRegistered ,
213+ {
214+ instanceId : 'i-4-still-booting' ,
215+ launchTime : moment ( new Date ( ) )
216+ . subtract ( MINIMUM_TIME_RUNNING - 3 , 'minutes' )
217+ . toDate ( ) ,
218+ type : 'Org' ,
219+ owner : ORG ,
220+ } ,
221+ {
222+ instanceId : 'i-5-orphan' ,
223+ launchTime : moment ( new Date ( ) )
224+ . subtract ( MINIMUM_TIME_RUNNING + 3 , 'minutes' )
225+ . toDate ( ) ,
226+ type : 'Org' ,
227+ owner : ORG ,
228+ } ,
229+ ] ) ;
230+
231+ await expect ( await adjust ( { poolSize : 2 } ) ) . resolves ;
232+ expect ( createRunners ) . not . toHaveBeenCalled ( ) ;
169233 } ) ;
170234 } ) ;
171235
@@ -175,9 +239,13 @@ describe('Test simple pool.', () => {
175239 } ) ;
176240
177241 it ( 'Top up if the pool size is set to 5' , async ( ) => {
178- const spy = jest . spyOn ( scale , 'createRunners' ) ;
179- await expect ( adjust ( { poolSize : 5 } ) ) . resolves ;
180- expect ( spy ) . toBeCalled ;
242+ await expect ( await adjust ( { poolSize : 5 } ) ) . resolves ;
243+ // 2 idle, top up with 3 to match a pool of 5
244+ expect ( createRunners ) . toHaveBeenCalledWith (
245+ expect . anything ( ) ,
246+ expect . objectContaining ( { numberOfRunners : 3 } ) ,
247+ expect . anything ( ) ,
248+ ) ;
181249 } ) ;
182250 } ) ;
183251} ) ;
0 commit comments