Remove __del__ functions and terminate connection explicitly.

Originally, we rely on __del__ to clean connection, close files, but
the __del__ function is not so reliable. It won't be called in some
corner cases. For example, the cyclic link trick that prevents to
trigger garbage collection.

Another case is that if we call logging function in __del__ of other
instance, and logging module is already close the logging file, then
it will reopen the logging file and truncate all content of the file.

Therefore, we replaced all __del__ method with "Close" and called it
explicitly before the program ends.

BUG=none
TEST=make test

Change-Id: I0e741585085de150306e04f2140ba1b8a0dd6bca
Reviewed-on: https://chromium-review.googlesource.com/455717
Commit-Ready: Chih-Yu Huang <akahuang@chromium.org>
Tested-by: Chih-Yu Huang <akahuang@chromium.org>
Reviewed-by: Joel Kitching <kitching@chromium.org>
diff --git a/graphyte/device.py b/graphyte/device.py
index 648f355..9e26eed 100644
--- a/graphyte/device.py
+++ b/graphyte/device.py
@@ -50,7 +50,9 @@
 
   def Terminate(self):
     """Terminates the device."""
-    raise NotImplementedError
+    if self.link is not None:
+      self.link.Close()
+      self.link = None
 
   def SetRF(self, rf_type):
     """Set the RF type."""
diff --git a/graphyte/graphyte.py b/graphyte/graphyte.py
index e5ec3ee..379be2e 100644
--- a/graphyte/graphyte.py
+++ b/graphyte/graphyte.py
@@ -93,7 +93,8 @@
     # Record all tests are pass or not
     self.all_tests_pass = None
 
-  def __del__(self):
+  def Close(self):
+    self.result_writer.Close()
     self.TerminateDevices()
 
   def _LoadDevice(self, graphyte_config, device_type, config_dir):
@@ -157,13 +158,18 @@
 
     It returns whether all tests are passed or not.
     """
+    # Initialize the test environment.
     self.all_tests_pass = True
     self.InitialzeDevices()
+
+    # Iteratively execute every test case.
     for test_case in self.testplan:
       self.RunTest(test_case)
-    self.TerminateDevices()
     logger.info('All tests are pass? %s', self.all_tests_pass)
     self.result_writer.WriteTotalResult(self.all_tests_pass)
+
+    # Terminate the test environment.
+    self.Close()
     return self.all_tests_pass
 
   def InitialzeDevices(self):
diff --git a/graphyte/link.py b/graphyte/link.py
index 3460ee6..6d18b60 100644
--- a/graphyte/link.py
+++ b/graphyte/link.py
@@ -31,6 +31,13 @@
 class DeviceLink(object):
   """An abstract class for Device (DUT and instrument) Links."""
 
+  def Close(self):
+    """Closes the connection of the link.
+
+    This method should be explicitly called before the device is destroyed.
+    """
+    pass
+
   def Push(self, local, remote):
     """Uploads a local file to DUT.
 
diff --git a/graphyte/links/gpib.py b/graphyte/links/gpib.py
index 4696b34..7707e6f 100644
--- a/graphyte/links/gpib.py
+++ b/graphyte/links/gpib.py
@@ -59,9 +59,10 @@
     except Exception as ex:
       raise ValueError('GPIBLink Init failed. %s' % ex)
 
-  def __del__(self):
+  def Close(self):
     if self.device is not None:
       self.device.close()
+      self.device = None
 
   def Push(self, local, remote):
     raise NotImplementedError
diff --git a/graphyte/links/scpi.py b/graphyte/links/scpi.py
index d47e039..cafbb6e 100644
--- a/graphyte/links/scpi.py
+++ b/graphyte/links/scpi.py
@@ -51,9 +51,6 @@
       raise SCPILinkError('Failed to connect %s:%d after %d tries' %
                           (host, port, retries))
 
-  def __del__(self):
-    self._Close()
-
   def Push(self, local, remote):
     raise NotImplementedError
 
@@ -121,12 +118,12 @@
       self.id = self._Query('*IDN?')
     except Exception:
       logger.exception('Unable to connect to %s:%d.', self.host, self.port)
-      self._Close()
+      self.Close()
       return False
     return True
 
-  def _Close(self):
-    logger.info('Destroying')
+  def Close(self):
+    logger.info('Close the SCPI socket.')
     if self.rfile:
       self.rfile.close()
       self.rfile = None
diff --git a/graphyte/result_writer.py b/graphyte/result_writer.py
index 87ca868..8c6f58e 100644
--- a/graphyte/result_writer.py
+++ b/graphyte/result_writer.py
@@ -22,8 +22,10 @@
     self.csv_writer = csv.DictWriter(self.fout, self.CSV_HEADER)
     self.csv_writer.writeheader()
 
-  def __del__(self):
-    self.fout.close()
+  def Close(self):
+    if self.fout is not None:
+      self.fout.close()
+      self.fout = None
 
   def WriteResult(self, test_case, result):
     """Writes the test result to csv file.