github.com/jingruilea/kubeedge@v1.2.0-beta.0.0.20200410162146-4bb8902b3879/edge/pkg/devicetwin/dtmanager/device_test.go (about) 1 /* 2 Copyright 2019 The KubeEdge Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package dtmanager 18 19 import ( 20 "encoding/json" 21 "errors" 22 "reflect" 23 "testing" 24 "time" 25 26 "github.com/astaxie/beego/orm" 27 "github.com/golang/mock/gomock" 28 29 beehiveContext "github.com/kubeedge/beehive/pkg/core/context" 30 "github.com/kubeedge/beehive/pkg/core/model" 31 "github.com/kubeedge/kubeedge/edge/mocks/beego" 32 "github.com/kubeedge/kubeedge/edge/pkg/common/dbm" 33 "github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtclient" 34 "github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon" 35 "github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext" 36 "github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype" 37 ) 38 39 var called bool 40 41 //testAction is a dummy function for testing Start 42 func testAction(context *dtcontext.DTContext, resource string, msg interface{}) (interface{}, error) { 43 called = true 44 return called, errors.New("Called the dummy function for testing") 45 } 46 47 // TestDeviceStartAction is function to test Start() when value is passed in ReceiverChan. 48 func TestDeviceStartAction(t *testing.T) { 49 beehiveContext.InitContext(beehiveContext.MsgCtxTypeChannel) 50 dtContextStateConnected, _ := dtcontext.InitDTContext() 51 dtContextStateConnected.State = dtcommon.Connected 52 content := dttype.DeviceUpdate{} 53 bytes, _ := json.Marshal(content) 54 msg := model.Message{Content: bytes} 55 56 receiveChanActionPresent := make(chan interface{}, 1) 57 receiveChanActionPresent <- &dttype.DTMessage{ 58 Action: "testAction", 59 Identity: "identity", 60 Msg: &msg, 61 } 62 63 receiveChanActionNotPresent := make(chan interface{}, 1) 64 receiveChanActionNotPresent <- &dttype.DTMessage{ 65 Action: "action", 66 Identity: "identity", 67 Msg: &model.Message{ 68 Content: "msg", 69 }, 70 } 71 tests := []struct { 72 name string 73 Worker Worker 74 }{ 75 { 76 name: "StartTest-ActionNotPresentInActionCallback", 77 Worker: Worker{ 78 ReceiverChan: receiveChanActionNotPresent, 79 DTContexts: dtContextStateConnected, 80 }, 81 }, 82 } 83 for _, test := range tests { 84 t.Run(test.name, func(t *testing.T) { 85 dw := DeviceWorker{ 86 Worker: test.Worker, 87 } 88 go dw.Start() 89 time.Sleep(1 * time.Millisecond) 90 //Adding a dummy function to callback to ensure Start is successful. 91 deviceActionCallBack["testAction"] = testAction 92 dw.ReceiverChan <- &dttype.DTMessage{ 93 Action: "testAction", 94 Identity: "identity", 95 Msg: &msg, 96 } 97 time.Sleep(1 * time.Millisecond) 98 if !called { 99 t.Errorf("Start failed") 100 } 101 }) 102 } 103 } 104 105 // TestDevicetHeartBeat is function to test Start() when value is passed in HeartBeatChan. 106 func TestDeviceStartHeartBeat(t *testing.T) { 107 beehiveContext.InitContext(beehiveContext.MsgCtxTypeChannel) 108 dtContexts, _ := dtcontext.InitDTContext() 109 heartChanStop := make(chan interface{}, 1) 110 heartChanPing := make(chan interface{}, 1) 111 heartChanStop <- "stop" 112 heartChanPing <- "ping" 113 tests := []struct { 114 name string 115 Worker Worker 116 Group string 117 }{ 118 { 119 name: "StartTest-PingInHeartBeatChannel", 120 Worker: Worker{ 121 HeartBeatChan: heartChanPing, 122 DTContexts: dtContexts, 123 }, 124 Group: "group", 125 }, 126 { 127 name: "StartTest-StopInHeartBeatChannel", 128 Worker: Worker{ 129 HeartBeatChan: heartChanStop, 130 DTContexts: dtContexts, 131 }, 132 Group: "group", 133 }, 134 } 135 for _, test := range tests { 136 t.Run(test.name, func(t *testing.T) { 137 dw := DeviceWorker{ 138 Worker: test.Worker, 139 Group: test.Group, 140 } 141 go dw.Start() 142 time.Sleep(1 * time.Millisecond) 143 if test.Worker.HeartBeatChan == heartChanPing { 144 _, exist := test.Worker.DTContexts.ModulesHealth.Load("group") 145 if !exist { 146 t.Errorf("Start Failed to add module in beehiveContext") 147 } 148 } 149 }) 150 } 151 } 152 153 // TestDealDeviceStatusUpdate test dealDeviceStatusUpdate 154 func TestDealDeviceStateUpdate(t *testing.T) { 155 var ormerMock *beego.MockOrmer 156 var querySeterMock *beego.MockQuerySeter 157 var emptyDevUpdate dttype.DeviceUpdate 158 beehiveContext.InitContext(beehiveContext.MsgCtxTypeChannel) 159 mockCtrl := gomock.NewController(t) 160 defer mockCtrl.Finish() 161 ormerMock = beego.NewMockOrmer(mockCtrl) 162 querySeterMock = beego.NewMockQuerySeter(mockCtrl) 163 dbm.DBAccess = ormerMock 164 165 dtContexts, err := dtcontext.InitDTContext() 166 if err != nil { 167 t.Errorf("InitDTContext error %v", err) 168 return 169 } 170 171 dtContexts.DeviceList.Store("DeviceC", "DeviceC") 172 deviceD := &dttype.Device{} 173 dtContexts.DeviceList.Store("DeviceD", deviceD) 174 bytesEmptyDevUpdate, err := json.Marshal(emptyDevUpdate) 175 if err != nil { 176 t.Errorf("marshal error %v", err) 177 return 178 } 179 devUpdate := &dttype.DeviceUpdate{State: "online"} 180 bytesDevUpdate, err := json.Marshal(devUpdate) 181 if err != nil { 182 t.Errorf("marshal error %v", err) 183 return 184 } 185 186 tests := []struct { 187 name string 188 context *dtcontext.DTContext 189 resource string 190 msg interface{} 191 want interface{} 192 wantErr error 193 // filterReturn is the return of mock interface querySeterMock's filter function 194 filterReturn orm.QuerySeter 195 // updateReturnInt is the first return of mock interface querySeterMock's update function 196 updateReturnInt int64 197 // updateReturnErr is the second return of mock interface querySeterMocks's update function also expected error 198 updateReturnErr error 199 // queryTableReturn is the return of mock interface ormerMock's QueryTable function 200 queryTableReturn orm.QuerySeter 201 times int 202 }{ 203 { 204 name: "dealDeviceStateUpdateTest-WrongMessageType", 205 context: dtContexts, 206 resource: "DeviceA", 207 msg: "", 208 want: nil, 209 wantErr: errors.New("msg not Message type"), 210 }, 211 { 212 name: "dealDeviceStateUpdateTest-DeviceDoesNotExist", 213 context: dtContexts, 214 resource: "DeviceB", 215 msg: &model.Message{Content: bytesEmptyDevUpdate}, 216 want: nil, 217 wantErr: nil, 218 }, 219 { 220 name: "dealDeviceStateUpdateTest-DeviceExist", 221 context: dtContexts, 222 resource: "DeviceC", 223 msg: &model.Message{Content: bytesEmptyDevUpdate}, 224 want: nil, 225 wantErr: nil, 226 }, 227 { 228 name: "dealDeviceStateUpdateTest-CorrectDeviceType", 229 context: dtContexts, 230 resource: "DeviceD", 231 msg: &model.Message{Content: bytesEmptyDevUpdate}, 232 want: nil, 233 wantErr: nil, 234 }, 235 { 236 name: "dealDeviceStateUpdateTest-UpdatePresent", 237 context: dtContexts, 238 resource: "DeviceD", 239 msg: &model.Message{Content: bytesDevUpdate}, 240 want: nil, 241 wantErr: nil, 242 filterReturn: querySeterMock, 243 updateReturnInt: int64(1), 244 updateReturnErr: nil, 245 queryTableReturn: querySeterMock, 246 times: 2, 247 }, 248 } 249 for _, test := range tests { 250 t.Run(test.name, func(t *testing.T) { 251 querySeterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(test.times) 252 querySeterMock.EXPECT().Update(gomock.Any()).Return(test.updateReturnInt, test.updateReturnErr).Times(test.times) 253 ormerMock.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(test.times) 254 got, err := dealDeviceStateUpdate(test.context, test.resource, test.msg) 255 if !reflect.DeepEqual(err, test.wantErr) { 256 t.Errorf("dealDeviceStateUpdate() error = %v, wantErr %v", err, test.wantErr) 257 return 258 } 259 if !reflect.DeepEqual(got, test.want) { 260 t.Errorf("dealDeviceStateUpdate() = %v, want %v", got, test.want) 261 } 262 }) 263 } 264 } 265 266 //TestDealDeviceUpdated is function to test dealDeviceUpdated(). 267 func TestDealDeviceUpdated(t *testing.T) { 268 beehiveContext.InitContext(beehiveContext.MsgCtxTypeChannel) 269 dtContexts, _ := dtcontext.InitDTContext() 270 content := dttype.DeviceUpdate{} 271 bytes, err := json.Marshal(content) 272 if err != nil { 273 t.Errorf("marshal error %v", err) 274 return 275 } 276 msg := model.Message{Content: bytes} 277 tests := []struct { 278 name string 279 context *dtcontext.DTContext 280 resource string 281 msg interface{} 282 want interface{} 283 wantErr error 284 }{ 285 { 286 name: "DealDeviceUpdatedTest-Wrong Message Type", 287 context: dtContexts, 288 resource: "Device", 289 msg: "", 290 want: nil, 291 wantErr: errors.New("msg not Message type"), 292 }, 293 { 294 name: "DealDeviceUpdatedTest-Correct Message Type", 295 context: dtContexts, 296 resource: "DeviceA", 297 msg: &msg, 298 want: nil, 299 wantErr: nil, 300 }, 301 } 302 for _, test := range tests { 303 t.Run(test.name, func(t *testing.T) { 304 got, err := dealDeviceUpdated(test.context, test.resource, test.msg) 305 if !reflect.DeepEqual(err, test.wantErr) { 306 t.Errorf("dealDeviceUpdated() error = %v, wantErr %v", err, test.wantErr) 307 return 308 } 309 if !reflect.DeepEqual(got, test.want) { 310 t.Errorf("dealDeviceUpdated() = %v, want %v", got, test.want) 311 } 312 }) 313 } 314 } 315 316 // TestDeviceUpdated is function to test DeviceUpdated(). 317 func TestDeviceUpdated(t *testing.T) { 318 var ormerMock *beego.MockOrmer 319 var querySeterMock *beego.MockQuerySeter 320 beehiveContext.InitContext(beehiveContext.MsgCtxTypeChannel) 321 mockCtrl := gomock.NewController(t) 322 defer mockCtrl.Finish() 323 ormerMock = beego.NewMockOrmer(mockCtrl) 324 querySeterMock = beego.NewMockQuerySeter(mockCtrl) 325 dbm.DBAccess = ormerMock 326 327 // adds is fake DeviceAttr used as argument 328 adds := make([]dtclient.DeviceAttr, 0) 329 // deletes is fake DeviceDelete used as argument 330 deletes := make([]dtclient.DeviceDelete, 0) 331 // updates is fake DeviceAttrUpdate used as argument 332 updates := make([]dtclient.DeviceAttrUpdate, 0) 333 adds = append(adds, dtclient.DeviceAttr{ 334 DeviceID: "Test", 335 }) 336 deletes = append(deletes, dtclient.DeviceDelete{ 337 DeviceID: "test", 338 Name: "test", 339 }) 340 updates = append(updates, dtclient.DeviceAttrUpdate{ 341 DeviceID: "test", 342 Name: "test", 343 Cols: make(map[string]interface{}), 344 }) 345 346 dtContexts, _ := dtcontext.InitDTContext() 347 dtContexts.DeviceList.Store("EmptyDevice", "Device") 348 349 devA := &dttype.Device{ID: "DeviceA"} 350 dtContexts.DeviceList.Store("DeviceA", devA) 351 352 messageAttributes := make(map[string]*dttype.MsgAttr) 353 optional := true 354 msgattr := &dttype.MsgAttr{ 355 Value: "ON", 356 Optional: &optional, 357 Metadata: &dttype.TypeMetadata{ 358 Type: "device", 359 }, 360 } 361 messageAttributes["DeviceA"] = msgattr 362 baseMessage := dttype.BuildBaseMessage() 363 364 tests := []struct { 365 name string 366 context *dtcontext.DTContext 367 deviceID string 368 attributes map[string]*dttype.MsgAttr 369 baseMessage dttype.BaseMessage 370 dealType int 371 want interface{} 372 wantErr error 373 // commitTimes is number of times commit is expected 374 commitTimes int 375 // beginTimes is number of times begin is expected 376 beginTimes int 377 // filterReturn is the return of mock interface querySeterMock's filter function 378 filterReturn orm.QuerySeter 379 // filterTimes is the number of times filter is called 380 filterTimes int 381 // insertReturnInt is the first return of mock interface ormerMock's Insert function 382 insertReturnInt int64 383 // insertReturnErr is the second return of mock interface ormerMock's Insert function 384 insertReturnErr error 385 // insertTimes is number of times Insert is expected 386 insertTimes int 387 // deleteReturnInt is the first return of mock interface ormerMock's Delete function 388 deleteReturnInt int64 389 // deleteReturnErr is the second return of mock interface ormerMock's Delete function 390 deleteReturnErr error 391 // deleteTimes is number of times Delete is expected 392 deleteTimes int 393 // updateReturnInt is the first return of mock interface ormerMock's Update function 394 updateReturnInt int64 395 // updateReturnErr is the second return of mock interface ormerMock's Update function 396 updateReturnErr error 397 // updateTimes is number of times Update is expected 398 updateTimes int 399 // queryTableReturn is the return of mock interface ormerMock's QueryTable function 400 queryTableReturn orm.QuerySeter 401 // queryTableTimes is the number of times queryTable is called 402 queryTableTimes int 403 }{ 404 { 405 name: "Test1", 406 context: dtContexts, 407 deviceID: "Device", 408 attributes: messageAttributes, 409 baseMessage: baseMessage, 410 want: nil, 411 wantErr: nil, 412 }, 413 { 414 name: "Test2", 415 context: dtContexts, 416 deviceID: "EmptyDevice", 417 attributes: messageAttributes, 418 baseMessage: baseMessage, 419 want: nil, 420 wantErr: nil, 421 }, 422 { 423 name: "Test3", 424 context: dtContexts, 425 deviceID: "DeviceA", 426 attributes: messageAttributes, 427 baseMessage: baseMessage, 428 wantErr: nil, 429 want: nil, 430 commitTimes: 1, 431 beginTimes: 1, 432 filterReturn: querySeterMock, 433 filterTimes: 0, 434 insertReturnInt: int64(1), 435 insertReturnErr: nil, 436 insertTimes: 1, 437 deleteReturnInt: int64(1), 438 deleteReturnErr: nil, 439 deleteTimes: 0, 440 updateReturnInt: int64(1), 441 updateReturnErr: nil, 442 updateTimes: 0, 443 queryTableReturn: querySeterMock, 444 queryTableTimes: 0, 445 }, 446 } 447 for _, test := range tests { 448 t.Run(test.name, func(t *testing.T) { 449 ormerMock.EXPECT().Commit().Return(nil).Times(test.commitTimes) 450 ormerMock.EXPECT().Begin().Return(nil).Times(test.beginTimes) 451 ormerMock.EXPECT().Insert(gomock.Any()).Return(test.insertReturnInt, test.insertReturnErr).Times(test.insertTimes) 452 ormerMock.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(test.queryTableTimes) 453 454 querySeterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(test.filterTimes) 455 querySeterMock.EXPECT().Delete().Return(test.deleteReturnInt, test.deleteReturnErr).Times(test.deleteTimes) 456 querySeterMock.EXPECT().Update(gomock.Any()).Return(test.updateReturnInt, test.updateReturnErr).Times(test.updateTimes) 457 458 got, err := DeviceUpdated(test.context, test.deviceID, test.attributes, test.baseMessage, test.dealType) 459 if !reflect.DeepEqual(err, test.wantErr) { 460 t.Errorf("DeviceUpdated() error = %v, wantErr %v", err, test.wantErr) 461 return 462 } 463 if !reflect.DeepEqual(got, test.want) { 464 t.Errorf("DeviceUpdated() failed Got = %v, want %v", got, test.want) 465 } 466 }) 467 } 468 } 469 470 // TestDealMsgAttr is function to test DealMsgAttr(). 471 func TestDealMsgAttr(t *testing.T) { 472 dtContextsEmptyAttributes, err := dtcontext.InitDTContext() 473 if err != nil { 474 t.Errorf("initDtcontext error %v", err) 475 return 476 } 477 dtContextsNonEmptyAttributes, err := dtcontext.InitDTContext() 478 if err != nil { 479 t.Errorf("initDtcontext error %v", err) 480 return 481 } 482 //Creating want and message attributes when device attribute is not present 483 devA := &dttype.Device{ID: "DeviceA"} 484 dtContextsEmptyAttributes.DeviceList.Store("DeviceA", devA) 485 486 messageAttributes := make(map[string]*dttype.MsgAttr) 487 optional := true 488 489 msgattr := &dttype.MsgAttr{ 490 Value: "ON", 491 Optional: &optional, 492 Metadata: &dttype.TypeMetadata{ 493 Type: "device", 494 }, 495 } 496 messageAttributes["DeviceA"] = msgattr 497 add := []dtclient.DeviceAttr{} 498 add = append(add, dtclient.DeviceAttr{ 499 ID: 0, 500 DeviceID: "DeviceA", 501 Name: "DeviceA", 502 Value: "ON", 503 Optional: true, 504 AttrType: "device", 505 Metadata: "{}", 506 }) 507 result := make(map[string]*dttype.MsgAttr) 508 result["DeviceA"] = msgattr 509 wantDealAttrResult := dttype.DealAttrResult{ 510 Add: add, 511 Delete: []dtclient.DeviceDelete{}, 512 Update: []dtclient.DeviceAttrUpdate{}, 513 Result: result, 514 Err: nil, 515 } 516 //Creating want and message attributes when device attribute is present 517 attributes := map[string]*dttype.MsgAttr{} 518 attributes["DeviceB"] = msgattr 519 attr := map[string]*dttype.MsgAttr{} 520 opt := false 521 attr["DeviceB"] = &dttype.MsgAttr{ 522 Value: "OFF", 523 Optional: &opt, 524 Metadata: &dttype.TypeMetadata{ 525 Type: "device", 526 }, 527 } 528 devB := &dttype.Device{ID: "DeviceB", Attributes: attr} 529 update := []dtclient.DeviceAttrUpdate{} 530 cols := make(map[string]interface{}) 531 cols["value"] = "ON" 532 upd := dtclient.DeviceAttrUpdate{ 533 Name: "DeviceB", 534 DeviceID: "DeviceB", 535 Cols: cols, 536 } 537 update = append(update, upd) 538 dtContextsNonEmptyAttributes.DeviceList.Store("DeviceB", devB) 539 want := dttype.DealAttrResult{ 540 Add: []dtclient.DeviceAttr{}, 541 Delete: []dtclient.DeviceDelete{}, 542 Update: update, 543 } 544 545 tests := []struct { 546 name string 547 context *dtcontext.DTContext 548 deviceID string 549 msgAttributes map[string]*dttype.MsgAttr 550 dealType int 551 want dttype.DealAttrResult 552 }{ 553 { 554 name: "DealMsgAttrTest-DeviceAttribute not present", 555 context: dtContextsEmptyAttributes, 556 deviceID: "DeviceA", 557 msgAttributes: messageAttributes, 558 want: wantDealAttrResult, 559 }, 560 { 561 name: "DealMsgAttrTest-DeviceAttribute present", 562 context: dtContextsNonEmptyAttributes, 563 deviceID: "DeviceB", 564 msgAttributes: attributes, 565 dealType: 1, 566 want: want, 567 }, 568 } 569 for _, tt := range tests { 570 t.Run(tt.name, func(t *testing.T) { 571 got := DealMsgAttr(tt.context, tt.deviceID, tt.msgAttributes, tt.dealType) 572 if !reflect.DeepEqual(got.Add, tt.want.Add) { 573 t.Errorf("Add error , Got = %v, Want = %v", got.Add, tt.want.Add) 574 return 575 } 576 if !reflect.DeepEqual(got.Delete, tt.want.Delete) { 577 t.Errorf("Delete error , Got = %v, Want = %v", got.Delete, tt.want.Delete) 578 return 579 } 580 if !reflect.DeepEqual(got.Update, tt.want.Update) { 581 t.Errorf("Update error , Got = %v, Want = %v", got.Update, tt.want.Update) 582 return 583 } 584 if !reflect.DeepEqual(got.Err, tt.want.Err) { 585 t.Errorf("Error error , Got = %v, Want = %v", got.Update, tt.want.Update) 586 return 587 } 588 for key, value := range tt.want.Result { 589 check := false 590 for key1, value1 := range got.Result { 591 if key == key1 { 592 if value == value1 { 593 check = true 594 break 595 } 596 } 597 if check == false { 598 t.Errorf("Wrong Map") 599 return 600 } 601 } 602 } 603 }) 604 } 605 }