1// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. 2// resty source code and usage is governed by a MIT style 3// license that can be found in the LICENSE file. 4 5package resty 6 7import ( 8 "compress/gzip" 9 "encoding/base64" 10 "encoding/json" 11 "encoding/xml" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "net/http" 16 "net/http/httptest" 17 "os" 18 "path/filepath" 19 "reflect" 20 "strconv" 21 "strings" 22 "sync/atomic" 23 "testing" 24 "time" 25) 26 27//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 28// Testing Unexported methods 29//___________________________________ 30 31func getTestDataPath() string { 32 pwd, _ := os.Getwd() 33 return filepath.Join(pwd, ".testdata") 34} 35 36func createGetServer(t *testing.T) *httptest.Server { 37 var attempt int32 38 var sequence int32 39 var lastRequest time.Time 40 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) { 41 t.Logf("Method: %v", r.Method) 42 t.Logf("Path: %v", r.URL.Path) 43 44 if r.Method == MethodGet { 45 switch r.URL.Path { 46 case "/": 47 _, _ = w.Write([]byte("TestGet: text response")) 48 case "/no-content": 49 _, _ = w.Write([]byte("")) 50 case "/json": 51 w.Header().Set("Content-Type", "application/json") 52 _, _ = w.Write([]byte(`{"TestGet": "JSON response"}`)) 53 case "/json-invalid": 54 w.Header().Set("Content-Type", "application/json") 55 _, _ = w.Write([]byte("TestGet: Invalid JSON")) 56 case "/long-text": 57 _, _ = w.Write([]byte("TestGet: text response with size > 30")) 58 case "/long-json": 59 w.Header().Set("Content-Type", "application/json") 60 _, _ = w.Write([]byte(`{"TestGet": "JSON response with size > 30"}`)) 61 case "/mypage": 62 w.WriteHeader(http.StatusBadRequest) 63 case "/mypage2": 64 _, _ = w.Write([]byte("TestGet: text response from mypage2")) 65 case "/set-retrycount-test": 66 attp := atomic.AddInt32(&attempt, 1) 67 if attp <= 3 { 68 time.Sleep(time.Second * 6) 69 } 70 _, _ = w.Write([]byte("TestClientRetry page")) 71 case "/set-retrywaittime-test": 72 // Returns time.Duration since last request here 73 // or 0 for the very first request 74 if atomic.LoadInt32(&attempt) == 0 { 75 lastRequest = time.Now() 76 _, _ = fmt.Fprint(w, "0") 77 } else { 78 now := time.Now() 79 sinceLastRequest := now.Sub(lastRequest) 80 lastRequest = now 81 _, _ = fmt.Fprintf(w, "%d", uint64(sinceLastRequest)) 82 } 83 atomic.AddInt32(&attempt, 1) 84 85 case "/set-retry-error-recover": 86 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8") 87 if atomic.LoadInt32(&attempt) == 0 { 88 w.WriteHeader(http.StatusTooManyRequests) 89 _, _ = w.Write([]byte(`{ "message": "too many" }`)) 90 } else { 91 _, _ = w.Write([]byte(`{ "message": "hello" }`)) 92 } 93 atomic.AddInt32(&attempt, 1) 94 case "/set-timeout-test-with-sequence": 95 seq := atomic.AddInt32(&sequence, 1) 96 time.Sleep(time.Second * 2) 97 _, _ = fmt.Fprintf(w, "%d", seq) 98 case "/set-timeout-test": 99 time.Sleep(time.Second * 6) 100 _, _ = w.Write([]byte("TestClientTimeout page")) 101 case "/my-image.png": 102 fileBytes, _ := ioutil.ReadFile(filepath.Join(getTestDataPath(), "test-img.png")) 103 w.Header().Set("Content-Type", "image/png") 104 w.Header().Set("Content-Length", strconv.Itoa(len(fileBytes))) 105 _, _ = w.Write(fileBytes) 106 case "/get-method-payload-test": 107 body, err := ioutil.ReadAll(r.Body) 108 if err != nil { 109 t.Errorf("Error: could not read get body: %s", err.Error()) 110 } 111 _, _ = w.Write(body) 112 case "/host-header": 113 _, _ = w.Write([]byte(r.Host)) 114 } 115 116 switch { 117 case strings.HasPrefix(r.URL.Path, "/v1/users/sample@sample.com/100002"): 118 if strings.HasSuffix(r.URL.Path, "details") { 119 _, _ = w.Write([]byte("TestGetPathParams: text response: " + r.URL.String())) 120 } else { 121 _, _ = w.Write([]byte("TestPathParamURLInput: text response: " + r.URL.String())) 122 } 123 } 124 125 } 126 }) 127 128 return ts 129} 130 131func handleLoginEndpoint(t *testing.T, w http.ResponseWriter, r *http.Request) { 132 if r.URL.Path == "/login" { 133 user := &User{} 134 135 // JSON 136 if IsJSONType(r.Header.Get(hdrContentTypeKey)) { 137 jd := json.NewDecoder(r.Body) 138 err := jd.Decode(user) 139 if r.URL.Query().Get("ct") == "problem" { 140 w.Header().Set(hdrContentTypeKey, "application/problem+json; charset=utf-8") 141 } else if r.URL.Query().Get("ct") == "rpc" { 142 w.Header().Set(hdrContentTypeKey, "application/json-rpc") 143 } else { 144 w.Header().Set(hdrContentTypeKey, "application/json") 145 } 146 147 if err != nil { 148 t.Logf("Error: %#v", err) 149 w.WriteHeader(http.StatusBadRequest) 150 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`)) 151 return 152 } 153 154 if user.Username == "testuser" && user.Password == "testpass" { 155 _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`)) 156 } else if user.Username == "testuser" && user.Password == "invalidjson" { 157 _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful", }`)) 158 } else { 159 w.WriteHeader(http.StatusUnauthorized) 160 _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`)) 161 } 162 163 return 164 } 165 166 // XML 167 if IsXMLType(r.Header.Get(hdrContentTypeKey)) { 168 xd := xml.NewDecoder(r.Body) 169 err := xd.Decode(user) 170 171 w.Header().Set(hdrContentTypeKey, "application/xml") 172 if err != nil { 173 t.Logf("Error: %v", err) 174 w.WriteHeader(http.StatusBadRequest) 175 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`)) 176 _, _ = w.Write([]byte(`<AuthError><Id>bad_request</Id><Message>Unable to read user info</Message></AuthError>`)) 177 return 178 } 179 180 if user.Username == "testuser" && user.Password == "testpass" { 181 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`)) 182 _, _ = w.Write([]byte(`<AuthSuccess><Id>success</Id><Message>login successful</Message></AuthSuccess>`)) 183 } else if user.Username == "testuser" && user.Password == "invalidxml" { 184 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`)) 185 _, _ = w.Write([]byte(`<AuthSuccess><Id>success</Id><Message>login successful</AuthSuccess>`)) 186 } else { 187 w.Header().Set("Www-Authenticate", "Protected Realm") 188 w.WriteHeader(http.StatusUnauthorized) 189 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`)) 190 _, _ = w.Write([]byte(`<AuthError><Id>unauthorized</Id><Message>Invalid credentials</Message></AuthError>`)) 191 } 192 193 return 194 } 195 } 196} 197 198func handleUsersEndpoint(t *testing.T, w http.ResponseWriter, r *http.Request) { 199 if r.URL.Path == "/users" { 200 // JSON 201 if IsJSONType(r.Header.Get(hdrContentTypeKey)) { 202 var users []ExampleUser 203 jd := json.NewDecoder(r.Body) 204 err := jd.Decode(&users) 205 w.Header().Set(hdrContentTypeKey, "application/json") 206 if err != nil { 207 t.Logf("Error: %v", err) 208 w.WriteHeader(http.StatusBadRequest) 209 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`)) 210 return 211 } 212 213 // logic check, since we are excepting to reach 3 records 214 if len(users) != 3 { 215 t.Log("Error: Excepted count of 3 records") 216 w.WriteHeader(http.StatusBadRequest) 217 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Expected record count doesn't match" }`)) 218 return 219 } 220 221 eu := users[2] 222 if eu.FirstName == "firstname3" && eu.ZipCode == "10003" { 223 w.WriteHeader(http.StatusAccepted) 224 _, _ = w.Write([]byte(`{ "message": "Accepted" }`)) 225 } 226 227 return 228 } 229 } 230} 231 232func createPostServer(t *testing.T) *httptest.Server { 233 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) { 234 t.Logf("Method: %v", r.Method) 235 t.Logf("Path: %v", r.URL.Path) 236 t.Logf("RawQuery: %v", r.URL.RawQuery) 237 t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey)) 238 239 if r.Method == MethodPost { 240 handleLoginEndpoint(t, w, r) 241 242 handleUsersEndpoint(t, w, r) 243 244 if r.URL.Path == "/usersmap" { 245 // JSON 246 if IsJSONType(r.Header.Get(hdrContentTypeKey)) { 247 if r.URL.Query().Get("status") == "500" { 248 body, err := ioutil.ReadAll(r.Body) 249 if err != nil { 250 t.Errorf("Error: could not read post body: %s", err.Error()) 251 } 252 t.Logf("Got query param: status=500 so we're returning the post body as response and a 500 status code. body: %s", string(body)) 253 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8") 254 w.WriteHeader(http.StatusInternalServerError) 255 _, _ = w.Write(body) 256 return 257 } 258 259 var users []map[string]interface{} 260 jd := json.NewDecoder(r.Body) 261 err := jd.Decode(&users) 262 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8") 263 if err != nil { 264 t.Logf("Error: %v", err) 265 w.WriteHeader(http.StatusBadRequest) 266 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`)) 267 return 268 } 269 270 // logic check, since we are excepting to reach 1 map records 271 if len(users) != 1 { 272 t.Log("Error: Excepted count of 1 map records") 273 w.WriteHeader(http.StatusBadRequest) 274 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Expected record count doesn't match" }`)) 275 return 276 } 277 278 w.WriteHeader(http.StatusAccepted) 279 _, _ = w.Write([]byte(`{ "message": "Accepted" }`)) 280 281 return 282 } 283 } 284 } 285 }) 286 287 return ts 288} 289 290func createFormPostServer(t *testing.T) *httptest.Server { 291 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) { 292 t.Logf("Method: %v", r.Method) 293 t.Logf("Path: %v", r.URL.Path) 294 t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey)) 295 296 if r.Method == MethodPost { 297 _ = r.ParseMultipartForm(10e6) 298 299 if r.URL.Path == "/profile" { 300 t.Logf("FirstName: %v", r.FormValue("first_name")) 301 t.Logf("LastName: %v", r.FormValue("last_name")) 302 t.Logf("City: %v", r.FormValue("city")) 303 t.Logf("Zip Code: %v", r.FormValue("zip_code")) 304 305 _, _ = w.Write([]byte("Success")) 306 return 307 } else if r.URL.Path == "/search" { 308 formEncodedData := r.Form.Encode() 309 t.Logf("Received Form Encoded values: %v", formEncodedData) 310 311 assertEqual(t, true, strings.Contains(formEncodedData, "search_criteria=pencil")) 312 assertEqual(t, true, strings.Contains(formEncodedData, "search_criteria=glass")) 313 314 _, _ = w.Write([]byte("Success")) 315 return 316 } else if r.URL.Path == "/upload" { 317 t.Logf("FirstName: %v", r.FormValue("first_name")) 318 t.Logf("LastName: %v", r.FormValue("last_name")) 319 320 targetPath := filepath.Join(getTestDataPath(), "upload") 321 _ = os.MkdirAll(targetPath, 0700) 322 323 for _, fhdrs := range r.MultipartForm.File { 324 for _, hdr := range fhdrs { 325 t.Logf("Name: %v", hdr.Filename) 326 t.Logf("Header: %v", hdr.Header) 327 dotPos := strings.LastIndex(hdr.Filename, ".") 328 329 fname := fmt.Sprintf("%s-%v%s", hdr.Filename[:dotPos], time.Now().Unix(), hdr.Filename[dotPos:]) 330 t.Logf("Write name: %v", fname) 331 332 infile, _ := hdr.Open() 333 f, err := os.OpenFile(filepath.Join(targetPath, fname), os.O_WRONLY|os.O_CREATE, 0666) 334 if err != nil { 335 t.Logf("Error: %v", err) 336 return 337 } 338 defer func() { 339 _ = f.Close() 340 }() 341 _, _ = io.Copy(f, infile) 342 343 _, _ = w.Write([]byte(fmt.Sprintf("File: %v, uploaded as: %v\n", hdr.Filename, fname))) 344 } 345 } 346 347 return 348 } 349 } 350 }) 351 352 return ts 353} 354 355func createFilePostServer(t *testing.T) *httptest.Server { 356 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) { 357 t.Logf("Method: %v", r.Method) 358 t.Logf("Path: %v", r.URL.Path) 359 t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey)) 360 361 if r.Method != MethodPost { 362 t.Log("createPostServer:: Not a Post request") 363 w.WriteHeader(http.StatusBadRequest) 364 fmt.Fprint(w, http.StatusText(http.StatusBadRequest)) 365 return 366 } 367 368 targetPath := filepath.Join(getTestDataPath(), "upload-large") 369 _ = os.MkdirAll(targetPath, 0700) 370 defer cleanupFiles(targetPath) 371 372 switch r.URL.Path { 373 case "/upload": 374 f, err := os.OpenFile(filepath.Join(targetPath, "large-file.png"), 375 os.O_WRONLY|os.O_CREATE, 0666) 376 if err != nil { 377 t.Logf("Error: %v", err) 378 return 379 } 380 defer func() { 381 _ = f.Close() 382 }() 383 size, _ := io.Copy(f, r.Body) 384 385 fmt.Fprintf(w, "File Uploaded successfully, file size: %v", size) 386 } 387 }) 388 389 return ts 390} 391 392func createAuthServer(t *testing.T) *httptest.Server { 393 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 394 t.Logf("Method: %v", r.Method) 395 t.Logf("Path: %v", r.URL.Path) 396 t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey)) 397 398 if r.Method == MethodGet { 399 if r.URL.Path == "/profile" { 400 // 004DDB79-6801-4587-B976-F093E6AC44FF 401 auth := r.Header.Get("Authorization") 402 t.Logf("Bearer Auth: %v", auth) 403 404 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8") 405 406 if !strings.HasPrefix(auth, "Bearer ") { 407 w.Header().Set("Www-Authenticate", "Protected Realm") 408 w.WriteHeader(http.StatusUnauthorized) 409 _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`)) 410 411 return 412 } 413 414 if auth[7:] == "004DDB79-6801-4587-B976-F093E6AC44FF" || auth[7:] == "004DDB79-6801-4587-B976-F093E6AC44FF-Request" { 415 _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`)) 416 } 417 } 418 419 return 420 } 421 422 if r.Method == MethodPost { 423 if r.URL.Path == "/login" { 424 auth := r.Header.Get("Authorization") 425 t.Logf("Basic Auth: %v", auth) 426 427 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8") 428 429 password, err := base64.StdEncoding.DecodeString(auth[6:]) 430 if err != nil || string(password) != "myuser:basicauth" { 431 w.Header().Set("Www-Authenticate", "Protected Realm") 432 w.WriteHeader(http.StatusUnauthorized) 433 _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`)) 434 435 return 436 } 437 438 _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`)) 439 } 440 441 return 442 } 443 })) 444 445 return ts 446} 447 448func createGenServer(t *testing.T) *httptest.Server { 449 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) { 450 t.Logf("Method: %v", r.Method) 451 t.Logf("Path: %v", r.URL.Path) 452 453 if r.Method == MethodGet { 454 if r.URL.Path == "/json-no-set" { 455 // Set empty header value for testing, since Go server sets to 456 // text/plain; charset=utf-8 457 w.Header().Set(hdrContentTypeKey, "") 458 _, _ = w.Write([]byte(`{"response":"json response no content type set"}`)) 459 } else if r.URL.Path == "/gzip-test" { 460 w.Header().Set(hdrContentTypeKey, plainTextType) 461 w.Header().Set(hdrContentEncodingKey, "gzip") 462 zw := gzip.NewWriter(w) 463 _, _ = zw.Write([]byte("This is Gzip response testing")) 464 zw.Close() 465 } else if r.URL.Path == "/gzip-test-gziped-empty-body" { 466 w.Header().Set(hdrContentTypeKey, plainTextType) 467 w.Header().Set(hdrContentEncodingKey, "gzip") 468 zw := gzip.NewWriter(w) 469 // write gziped empty body 470 _, _ = zw.Write([]byte("")) 471 zw.Close() 472 } else if r.URL.Path == "/gzip-test-no-gziped-body" { 473 w.Header().Set(hdrContentTypeKey, plainTextType) 474 w.Header().Set(hdrContentEncodingKey, "gzip") 475 // don't write body 476 } 477 478 return 479 } 480 481 if r.Method == MethodPut { 482 if r.URL.Path == "/plaintext" { 483 _, _ = w.Write([]byte("TestPut: plain text response")) 484 } else if r.URL.Path == "/json" { 485 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8") 486 _, _ = w.Write([]byte(`{"response":"json response"}`)) 487 } else if r.URL.Path == "/xml" { 488 w.Header().Set(hdrContentTypeKey, "application/xml") 489 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?><Response>XML response</Response>`)) 490 } 491 return 492 } 493 494 if r.Method == MethodOptions && r.URL.Path == "/options" { 495 w.Header().Set("Access-Control-Allow-Origin", "localhost") 496 w.Header().Set("Access-Control-Allow-Methods", "PUT, PATCH") 497 w.Header().Set("Access-Control-Expose-Headers", "x-go-resty-id") 498 w.WriteHeader(http.StatusOK) 499 return 500 } 501 502 if r.Method == MethodPatch && r.URL.Path == "/patch" { 503 w.WriteHeader(http.StatusOK) 504 return 505 } 506 507 if r.Method == "REPORT" && r.URL.Path == "/report" { 508 body, _ := ioutil.ReadAll(r.Body) 509 if len(body) == 0 { 510 w.WriteHeader(http.StatusOK) 511 } 512 return 513 } 514 }) 515 516 return ts 517} 518 519func createRedirectServer(t *testing.T) *httptest.Server { 520 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) { 521 t.Logf("Method: %v", r.Method) 522 t.Logf("Path: %v", r.URL.Path) 523 524 if r.Method == MethodGet { 525 if strings.HasPrefix(r.URL.Path, "/redirect-host-check-") { 526 cntStr := strings.SplitAfter(r.URL.Path, "-")[3] 527 cnt, _ := strconv.Atoi(cntStr) 528 529 if cnt != 7 { // Testing hard stop via logical 530 if cnt >= 5 { 531 http.Redirect(w, r, "http://httpbin.org/get", http.StatusTemporaryRedirect) 532 } else { 533 http.Redirect(w, r, fmt.Sprintf("/redirect-host-check-%d", cnt+1), http.StatusTemporaryRedirect) 534 } 535 } 536 } else if strings.HasPrefix(r.URL.Path, "/redirect-") { 537 cntStr := strings.SplitAfter(r.URL.Path, "-")[1] 538 cnt, _ := strconv.Atoi(cntStr) 539 540 http.Redirect(w, r, fmt.Sprintf("/redirect-%d", cnt+1), http.StatusTemporaryRedirect) 541 } 542 } 543 }) 544 545 return ts 546} 547 548func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest.Server { 549 return httptest.NewServer(http.HandlerFunc(fn)) 550} 551 552func dc() *Client { 553 c := New(). 554 outputLogTo(ioutil.Discard) 555 return c 556} 557 558func dcl() *Client { 559 c := New(). 560 SetDebug(true). 561 outputLogTo(ioutil.Discard) 562 return c 563} 564 565func dcr() *Request { 566 return dc().R() 567} 568 569func dclr() *Request { 570 c := dc(). 571 SetDebug(true). 572 outputLogTo(ioutil.Discard) 573 return c.R() 574} 575 576func assertNil(t *testing.T, v interface{}) { 577 if !isNil(v) { 578 t.Errorf("[%v] was expected to be nil", v) 579 } 580} 581 582func assertNotNil(t *testing.T, v interface{}) { 583 if isNil(v) { 584 t.Errorf("[%v] was expected to be non-nil", v) 585 } 586} 587 588func assertType(t *testing.T, typ, v interface{}) { 589 if reflect.DeepEqual(reflect.TypeOf(typ), reflect.TypeOf(v)) { 590 t.Errorf("Expected type %t, got %t", typ, v) 591 } 592} 593 594func assertError(t *testing.T, err error) { 595 if err != nil { 596 t.Errorf("Error occurred [%v]", err) 597 } 598} 599 600func assertEqual(t *testing.T, e, g interface{}) (r bool) { 601 if !equal(e, g) { 602 t.Errorf("Expected [%v], got [%v]", e, g) 603 } 604 605 return 606} 607 608func assertNotEqual(t *testing.T, e, g interface{}) (r bool) { 609 if equal(e, g) { 610 t.Errorf("Expected [%v], got [%v]", e, g) 611 } else { 612 r = true 613 } 614 615 return 616} 617 618func equal(expected, got interface{}) bool { 619 return reflect.DeepEqual(expected, got) 620} 621 622func isNil(v interface{}) bool { 623 if v == nil { 624 return true 625 } 626 627 rv := reflect.ValueOf(v) 628 kind := rv.Kind() 629 if kind >= reflect.Chan && kind <= reflect.Slice && rv.IsNil() { 630 return true 631 } 632 633 return false 634} 635 636func logResponse(t *testing.T, resp *Response) { 637 t.Logf("Response Status: %v", resp.Status()) 638 t.Logf("Response Time: %v", resp.Time()) 639 t.Logf("Response Headers: %v", resp.Header()) 640 t.Logf("Response Cookies: %v", resp.Cookies()) 641 t.Logf("Response Body: %v", resp) 642} 643 644func cleanupFiles(files ...string) { 645 pwd, _ := os.Getwd() 646 647 for _, f := range files { 648 if filepath.IsAbs(f) { 649 _ = os.RemoveAll(f) 650 } else { 651 _ = os.RemoveAll(filepath.Join(pwd, f)) 652 } 653 } 654} 655