github.com/sacloud/iaas-api-go@v1.12.0/testutil/crud.go (about) 1 // Copyright 2022-2023 The sacloud/iaas-api-go Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package testutil 16 17 import ( 18 "context" 19 "encoding/json" 20 "log" 21 "net/http" 22 "runtime/debug" 23 "sync" 24 "time" 25 26 "github.com/sacloud/iaas-api-go" 27 "github.com/sacloud/iaas-api-go/accessor" 28 "github.com/sacloud/iaas-api-go/types" 29 ) 30 31 // CRUDTestCase CRUD操作テストケース 32 type CRUDTestCase struct { 33 // PreCheck テスト実行 or スキップを判定するためのFunc 34 PreCheck func(TestT) 35 36 // APICallerのセットアップ用Func、テストケースごとに1回呼ばれる 37 SetupAPICallerFunc func() iaas.APICaller 38 39 // Setup テスト前の準備(依存リソースの作成など)を行うためのFunc(省略可) 40 Setup func(*CRUDTestContext, iaas.APICaller) error 41 42 // Create Create操作のテスト用Func(省略可) 43 Create *CRUDTestFunc 44 45 // Read Read操作のテスト用Func(必須) 46 Read *CRUDTestFunc 47 48 // Updates Update操作のテスト用Func(省略可) 49 Updates []*CRUDTestFunc 50 51 // Shutdown Delete操作の前のシャットダウン(省略可) 52 Shutdown func(*CRUDTestContext, iaas.APICaller) error 53 54 // Delete Delete操作のテスト用Func(省略可) 55 Delete *CRUDTestDeleteFunc 56 57 // Cleanup APIで作成/変更したリソースなどのクリーンアップ用Func(省略化) 58 Cleanup func(*CRUDTestContext, iaas.APICaller) error 59 60 // Parallel t.Parallelを呼ぶかのフラグ 61 Parallel bool 62 63 // IgnoreStartupWait リソース作成後の起動待ちを行わない 64 IgnoreStartupWait bool 65 } 66 67 // CRUDTestContext CRUD操作テストでのコンテキスト、一連のテスト中に共有される 68 type CRUDTestContext struct { 69 // ID CRUDテスト対象リソースのID 70 // 71 // Create/Read/Updateの戻り値がidAccessorの場合に各操作の後で設定される 72 ID types.ID 73 74 // Values 一連のテスト中に共有したい値 75 // 76 // 依存リソースのIDの保持などで利用する 77 Values map[string]interface{} 78 79 // LastValue 最後の操作での戻り値 80 LastValue interface{} 81 82 ctx context.Context 83 once sync.Once 84 } 85 86 func (c *CRUDTestContext) initInnerContext() { 87 c.once.Do(func() { 88 c.ctx = context.TODO() 89 }) 90 } 91 92 // Deadline context.Context実装 93 func (c *CRUDTestContext) Deadline() (deadline time.Time, ok bool) { 94 c.initInnerContext() 95 return c.ctx.Deadline() 96 } 97 98 // Done context.Context実装 99 func (c *CRUDTestContext) Done() <-chan struct{} { 100 c.initInnerContext() 101 return c.ctx.Done() 102 } 103 104 // Err context.Context実装 105 func (c *CRUDTestContext) Err() error { 106 c.initInnerContext() 107 return c.ctx.Err() 108 } 109 110 // Value context.Context実装 111 func (c *CRUDTestContext) Value(key interface{}) interface{} { 112 c.initInnerContext() 113 return c.ctx.Value(key) 114 } 115 116 // CRUDTestFunc CRUD操作(DELETE以外)テストでのテスト用Func 117 type CRUDTestFunc struct { 118 // Func API操作を行うFunc 119 Func func(*CRUDTestContext, iaas.APICaller) (interface{}, error) 120 121 // CheckFunc 任意のチェックを行うためのFunc、省略可能。 122 CheckFunc func(TestT, *CRUDTestContext, interface{}) error 123 124 // SkipExtractID Trueの場合Funcの戻り値からのID抽出を行わない 125 SkipExtractID bool 126 } 127 128 // CRUDTestDeleteFunc CRUD操作テストのDeleteテスト用Func 129 type CRUDTestDeleteFunc struct { 130 // Func API操作を行うFunc 131 Func func(*CRUDTestContext, iaas.APICaller) error 132 } 133 134 // CRUDTestExpect CRUD操作(DELETE以外)テストでの期待値 135 type CRUDTestExpect struct { 136 // ExpectValue CRUD操作実行後の期待値 137 ExpectValue interface{} 138 139 // IgnoreFields比較時に無視する項目 140 IgnoreFields []string 141 } 142 143 // Prepare テスト対象値を受け取り、比較可能な状態に加工した対象値と期待値を返す 144 func (c *CRUDTestExpect) Prepare(actual interface{}) (interface{}, interface{}) { 145 toMap := func(v interface{}) map[string]interface{} { 146 data, err := json.Marshal(v) 147 if err != nil { 148 log.Fatalf("prepare is failed: json.Marshal returned error: %s", err) 149 } 150 var m map[string]interface{} 151 if err := json.Unmarshal(data, &m); err != nil { 152 log.Fatalf("prepare is failed: json.Unmarshal returned error: %s", err) 153 } 154 for _, key := range c.IgnoreFields { 155 delete(m, key) 156 } 157 return m 158 } 159 160 return toMap(c.ExpectValue), toMap(actual) 161 } 162 163 // RunCRUD 任意の条件でCRUD操作をテストする 164 func RunCRUD(t TestT, testCase *CRUDTestCase) { 165 if testCase.SetupAPICallerFunc == nil { 166 t.Fatal("CRUDTestCase.SetupAPICallerFunc is required") 167 } 168 169 if testCase.Parallel { 170 t.Parallel() 171 } 172 173 if testCase.PreCheck != nil { 174 testCase.PreCheck(t) 175 } 176 177 testContext := &CRUDTestContext{ 178 Values: make(map[string]interface{}), 179 } 180 defer func() { 181 // Cleanup 182 if testCase.Cleanup != nil { 183 if err := testCase.Cleanup(testContext, testCase.SetupAPICallerFunc()); err != nil { 184 t.Logf("Cleanup is failed: ", err) 185 } 186 } 187 if err := recover(); err != nil { 188 t.Logf("Unexpected error is occurred: %v, Trace: %s", err, string(debug.Stack())) 189 } 190 }() 191 192 if testCase.Setup != nil { 193 if err := testCase.Setup(testContext, testCase.SetupAPICallerFunc()); err != nil { 194 t.Error("Setup is failed: ", err) 195 return 196 } 197 } 198 199 testFunc := func(f *CRUDTestFunc) error { 200 actual, err := f.Func(testContext, testCase.SetupAPICallerFunc()) 201 if err != nil { 202 return err 203 } 204 testContext.LastValue = actual 205 206 if actual != nil && f.CheckFunc != nil { 207 if err := f.CheckFunc(t, testContext, actual); err != nil { 208 return err 209 } 210 } 211 212 // extract ID from result of f.Func() 213 if actual != nil && !f.SkipExtractID { 214 if idHolder, ok := actual.(accessor.ID); ok { 215 testContext.ID = idHolder.GetID() 216 } 217 } 218 219 return nil 220 } 221 222 // Create 223 if testCase.Create != nil { 224 if err := testFunc(testCase.Create); err != nil { 225 t.Error("Create is failed: ", err) 226 return 227 } 228 229 if !testCase.IgnoreStartupWait && testCase.Read != nil && testContext.LastValue != nil { 230 waiter := iaas.WaiterForApplianceUp(func() (interface{}, error) { 231 return testCase.Read.Func(testContext, testCase.SetupAPICallerFunc()) 232 }, 100) 233 if _, err := waiter.WaitForState(context.TODO()); err != nil { 234 t.Error("WaitForUp is failed: ", err) 235 return 236 } 237 } 238 } 239 240 // Read 241 if testCase.Read != nil { 242 if err := testFunc(testCase.Read); err != nil { 243 t.Fatal("Read is failed: ", err) 244 } 245 } 246 247 // Updates 248 for _, updFunc := range testCase.Updates { 249 if err := testFunc(updFunc); err != nil { 250 t.Error("Update is failed: ", err) 251 return 252 } 253 } 254 255 // Shutdown 256 if testCase.Shutdown != nil { 257 if testCase.Read == nil { 258 t.Log("CRUDTestCase.Shutdown is set, but CRUDTestCase.Read is nil. Shutdown is skipped") 259 } else { 260 v, err := testCase.Read.Func(testContext, testCase.SetupAPICallerFunc()) 261 if err != nil { 262 t.Error("Shutdown is failed: ", err) 263 return 264 } 265 if v, ok := v.(accessor.InstanceStatus); ok && v.GetInstanceStatus().IsUp() { 266 if err := testCase.Shutdown(testContext, testCase.SetupAPICallerFunc()); err != nil { 267 t.Error("Shutdown is failed: ", err) 268 return 269 } 270 271 waiter := iaas.WaiterForDown(func() (interface{}, error) { 272 return testCase.Read.Func(testContext, testCase.SetupAPICallerFunc()) 273 }) 274 if _, err := waiter.WaitForState(context.TODO()); err != nil { 275 t.Error("WaitForDown is failed: ", err) 276 return 277 } 278 } 279 } 280 } 281 282 // Delete 283 if testCase.Delete != nil { 284 if err := testCase.Delete.Func(testContext, testCase.SetupAPICallerFunc()); err != nil { 285 t.Error("Delete is failed: ", err) 286 return 287 } 288 if testCase.Read != nil { 289 // check not exists 290 _, err := testCase.Read.Func(testContext, testCase.SetupAPICallerFunc()) 291 if err == nil { 292 t.Error("Resource still exists: ", testContext.ID) 293 return 294 } 295 if e, ok := err.(iaas.APIError); ok { 296 if e.ResponseCode() != http.StatusNotFound { 297 t.Error("Reading after delete is failed: ", e) 298 return 299 } 300 } 301 } 302 } 303 }