Merge pull request #689 from pushkarnk/task-cancel

diff --git a/Foundation/NSURLSession/NSURLSessionTask.swift b/Foundation/NSURLSession/NSURLSessionTask.swift
index 6f33cc6..5856d5d 100644
--- a/Foundation/NSURLSession/NSURLSessionTask.swift
+++ b/Foundation/NSURLSession/NSURLSessionTask.swift
@@ -194,8 +194,18 @@
      * cases, the task may signal other work before it acknowledges the
      * cancelation.  -cancel may be sent to a task that has been suspended.
      */
-    open func cancel() { NSUnimplemented() }
-    
+    open func cancel() {
+        workQueue.sync {
+            guard self.state == .running || self.state == .suspended else { return }
+            self.state = .canceling
+            self.workQueue.async {
+                self.internalState = .transferFailed
+                let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: nil))
+                self.completeTask(withError: urlError)
+            }
+        }
+    }
+
     /*
      * The current state of the task within the session.
      */
@@ -866,7 +876,7 @@
     }
     func completeTask(withError error: Error) {
         self.error = error
-        
+
         guard case .transferFailed = internalState else {
             fatalError("Trying to complete the task, but its transfer isn't complete / failed.")
         }
diff --git a/TestFoundation/TestNSURLSession.swift b/TestFoundation/TestNSURLSession.swift
index d86e749..cce1508 100644
--- a/TestFoundation/TestNSURLSession.swift
+++ b/TestFoundation/TestNSURLSession.swift
@@ -32,6 +32,7 @@
             ("test_finishTaskAndInvalidate", test_finishTasksAndInvalidate),
             ("test_taskError", test_taskError),
             ("test_taskCopy", test_taskCopy),
+            ("test_cancelTask", test_cancelTask),
         ]
     }
 
@@ -297,6 +298,26 @@
         
         XCTAssert(task.isEqual(task.copy()))
     }
+
+    func test_cancelTask() {
+        let serverReady = ServerSemaphore()
+        globalDispatchQueue.async {
+            do {
+                try self.runServer(with: serverReady)
+            } catch {
+                XCTAssertTrue(true)
+                return
+            }
+        }
+        serverReady.wait()
+        let url = URL(string: "http://127.0.0.1:\(serverPort)/Peru")!
+        let d = DataTask(with: expectation(description: "Task to be canceled"))
+        d.cancelExpectation = expectation(description: "URLSessionTask wasn't canceled")
+        d.run(with: url)
+        d.cancel()
+        waitForExpectations(timeout: 12)
+    }
+
 }
 
 class SessionDelegate: NSObject, URLSessionDelegate {
@@ -314,6 +335,8 @@
     var capital = "unknown"
     var session: URLSession! = nil
     var task: URLSessionDataTask! = nil
+    var cancelExpectation: XCTestExpectation?
+
     public var error = false
 
     init(with expectation: XCTestExpectation) {
@@ -335,20 +358,26 @@
         task = session.dataTask(with: url)
         task.resume()
     }
+
+    func cancel() {
+        task.cancel()
+    }
 }
 
 extension DataTask : URLSessionDataDelegate {
-     public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
-         capital = String(data: data, encoding: String.Encoding.utf8)!
-         dataTaskExpectation.fulfill()
-     }
+    public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
+        capital = String(data: data, encoding: String.Encoding.utf8)!
+        dataTaskExpectation.fulfill()
+    }
 }
 
 extension DataTask : URLSessionTaskDelegate {
     public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
          guard let e = error as? URLError else { return }
-         XCTAssertEqual(e.code, .timedOut, "Unexpected error code")
          dataTaskExpectation.fulfill()
+         if let cancellation = cancelExpectation {
+             cancellation.fulfill()
+         }
          self.error = true
      }
 }