Repository: spyder-ide/spyder

Issue:
When comparing a generated and loaded profile, any negative deltas show (actual_value - ?) 1h:60min, not actual value
Load data to compare when the loaded data is faster it shows 1h60min,espically the figure is so small,for example 5 ms compare to 3ms,it shows -1h:60min
![default](https://user-images.githubusercontent.com/34744187/34260615-8a4b27fa-e6a1-11e7-972a-b74415b7a9dd.PNG)
**u can easily reproduce it by doing some quick things**

```python
import time
def delay(howlong):
    time.sleep(howlong)
delay(0.005)
print('delay finish')
```

## Version and main components

* Spyder Version: 3.2.4 
* Python Version: 3.6.3
* Qt Versions: 5.9.3, PyQt5 5.9.2 on Windows

## Dependencies
```
pyflakes >=0.6.0 :  1.6.0 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pygments >=2.0   :  2.2.0 (OK)
pandas >=0.13.1  :  0.21.0 (OK)
numpy >=1.7      :  1.13.3 (OK)
sphinx >=0.6.6   :  1.6.5 (OK)
rope >=0.9.4     :  0.10.7 (OK)
jedi >=0.9.0     :  0.11.1 (OK)
nbconvert >=4.0  :  5.3.1 (OK)
sympy >=0.7.3    :  None (NOK)
cython >=0.21    :  None (NOK)
qtconsole >=4.2.0:  4.3.1 (OK)
IPython >=4.0    :  6.2.1 (OK)
pylint >=0.25    :  1.8.1 (OK)

```



Discussion:
Please describe step by step how you got this error so we can reproduce it in our side. If you fail to do that, we'll close this issue in a week from now.
Unfortunately your description is not clear enough.
![default](https://user-images.githubusercontent.com/34744187/34260615-8a4b27fa-e6a1-11e7-972a-b74415b7a9dd.PNG)
so sorry i forget the pic once u see the pic u know what i am talking about.
@ccordoba12 Reproduced. This seems to indeed be a bug, from what I can tell.

![image](https://user-images.githubusercontent.com/17051931/34264707-608e51fc-e639-11e7-8890-a1b6536167f5.png)


Resolving patch:
diff --git a/runtests.py b/runtests.py
--- a/runtests.py
+++ b/runtests.py
@@ -24,8 +24,11 @@ def main():
     """
     Run pytest tests.
     """
-    errno = pytest.main(['-x', 'spyder',  '-v', '-rw', '--durations=10',
-                         '--cov=spyder', '--cov-report=term-missing'])
+    errno = pytest.main(['-x', 'spyder',  'spyder_profiler',
+                         '-vv', '-rw',
+                         '--durations=10', '--cov=spyder',
+                         '--cov=spyder_profiler',
+                         '--cov-report=term-missing'])
 
     # sys.exit doesn't work here because some things could be running
     # in the background (e.g. closing the main window) when this point
diff --git a/spyder_profiler/widgets/profilergui.py b/spyder_profiler/widgets/profilergui.py
--- a/spyder_profiler/widgets/profilergui.py
+++ b/spyder_profiler/widgets/profilergui.py
@@ -17,6 +17,7 @@
 from __future__ import with_statement
 import os
 import os.path as osp
+from itertools import islice
 import sys
 import time
 import re
@@ -535,8 +536,12 @@ def function_info(self, functionKey):
             file_and_line = '%s : %d' % (filename, line_number)
         return filename, line_number, function_name, file_and_line, node_type
 
-    def format_measure(self, measure):
+    @staticmethod
+    def format_measure(measure):
         """Get format and units for data coming from profiler task."""
+        # Convert to a positive value.
+        measure = abs(measure)
+
         # For number of calls
         if isinstance(measure, int):
             return to_text_string(measure)
@@ -563,29 +568,43 @@ def format_measure(self, measure):
             measure = u"{0:.0f}h:{1:.0f}min".format(h, m)
         return measure
 
-    def color_string(self, args):
-        x, fmt = args
+    def color_string(self, x):
+        """Return a string formatted delta for the values in x.
+
+        Args:
+            x: 2-item list of integers (representing number of calls) or
+               2-item list of floats (representing seconds of runtime).
+
+        Returns:
+            A list with [formatted x[0], [color, formatted delta]], where
+            color reflects whether x[1] is lower, greater, or the same as
+            x[0].
+        """
         diff_str = ""
         color = "black"
 
         if len(x) == 2 and self.compare_file is not None:
             difference = x[0] - x[1]
-            if difference < 0:
-                diff_str = "".join(['', fmt[1] % self.format_measure(difference)])
-                color = "green"
-            elif difference > 0:
-                diff_str = "".join(['+', fmt[1] % self.format_measure(difference)])
-                color = "red"
-        return [fmt[0] % self.format_measure(x[0]), [diff_str, color]]
+            if difference:
+                color, sign = ('green', '-') if difference < 0 else ('red', '+')
+                diff_str = '{}{}'.format(sign, self.format_measure(difference))
+        return [self.format_measure(x[0]), [diff_str, color]]
 
     def format_output(self, child_key):
-        """ Formats the data"""
-        if True:
-            data = [x.stats.get(child_key, [0,0,0,0,0]) for x in self.stats1]
-            format_data = zip(list(zip(*data))[1:4],
-                              [["%s"]*2, ["%s", "%s"], ["%s", "%s"]])
-            return (map(self.color_string, format_data))
-            
+        """ Formats the data.
+
+        self.stats1 contains a list of one or two pstat.Stats() instances, with
+        the first being the current run and the second, the saved run, if it
+        exists.  Each Stats instance is a dictionary mapping a function to
+        5 data points - cumulative calls, number of calls, total time,
+        cumulative time, and callers.
+
+        format_output() converts the number of calls, total time, and
+        cumulative time to a string format for the child_key parameter.
+        """
+        data = [x.stats.get(child_key, [0, 0, 0, 0, {}]) for x in self.stats1]
+        return (map(self.color_string, islice(zip(*data), 1, 4)))
+
     def populate_tree(self, parentItem, children_list):
         """Recursive method to create each item (and associated data) in the tree."""
         for child_key in children_list:


Test patch:
diff --git a/spyder_profiler/widgets/tests/test_profiler.py b/spyder_profiler/widgets/tests/test_profiler.py
new file mode 100644
--- /dev/null
+++ b/spyder_profiler/widgets/tests/test_profiler.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+#
+
+"""
+Tests for profiler.py
+"""
+
+
+# Standard library imports
+try:
+    from unittest.mock import Mock
+except ImportError:
+    from mock import Mock  # Python 2
+
+# Third party imports
+import pytest
+
+# Local imports
+from spyder_profiler.widgets import profilergui
+
+
+# --- Helper methods
+# -----------------------------------------------------------------------------
+
+
+# --- Fixtures
+# -----------------------------------------------------------------------------
+@pytest.fixture
+def profiler_datatree_bot(qtbot):
+    """Set up Profiler widget."""
+    tree = profilergui.ProfilerDataTree(None)
+    qtbot.addWidget(tree)
+    tree.show()
+    yield tree
+    tree.destroy()
+
+
+# --- Tests
+# -----------------------------------------------------------------------------
+def test_format_measure(profiler_datatree_bot):
+    """ Test ProfilerDataTree.format_measure()."""
+    tree = profiler_datatree_bot
+    fm = tree.format_measure
+    assert fm(125) == '125'
+    assert fm(1.25e-8) == '12.50 ns'
+    assert fm(1.25e-5) == '12.50 us'
+    assert fm(1.25e-2) == '12.50 ms'
+    assert fm(12.5) == '12.50 sec'
+    assert fm(125.5) == '2.5 min'
+    assert fm(12555.5) == '3h:29min'
+
+    assert fm(-125) == '125'
+    assert fm(-1.25e-8) == '12.50 ns'
+    assert fm(-1.25e-5) == '12.50 us'
+    assert fm(-1.25e-2) == '12.50 ms'
+    assert fm(-12.5) == '12.50 sec'
+    assert fm(-125.5) == '2.5 min'
+    assert fm(-12555.5) == '3h:29min'
+
+
+def test_color_string(profiler_datatree_bot):
+    """ Test ProfilerDataTree.color_string()."""
+    tree = profiler_datatree_bot
+    cs = tree.color_string
+
+    tree.compare_file = 'test'
+    assert cs([5.0]) == ['5.00 sec', ['', 'black']]
+    assert cs([1.251e-5, 1.251e-5]) == ['12.51 us', ['', 'black']]
+    assert cs([5.0, 4.0]) == ['5.00 sec', ['+1000.00 ms', 'red']]
+    assert cs([4.0, 5.0]) == ['4.00 sec', ['-1000.00 ms', 'green']]
+
+    tree.compare_file = None
+    assert cs([4.0, 5.0]) == ['4.00 sec', ['', 'black']]
+
+
+def test_format_output(profiler_datatree_bot):
+    """ Test ProfilerDataTree.format_output()."""
+    tree = profiler_datatree_bot
+    fo = tree.format_output
+
+    # Mock Stats class to be able to use fixed data for input.
+    class Stats:
+        stats = {}
+
+    tree.stats1 = [Stats(), Stats()]
+    tree.stats1[0].stats = {('key1'): (1, 1000, 3.5, 1.5, {}),
+                            ('key2'): (1, 1200, 2.0, 2.0, {})
+                            }
+    tree.stats1[1].stats = {('key1'): (1, 1000, 3.7, 1.3, {}),
+                            ('key2'): (1, 1199, 2.4, 2.4, {})
+                            }
+
+    tree.compare_file = 'test'
+    assert list((fo('key1'))) == [['1000', ['', 'black']],
+                                  ['3.50 sec', ['-200.00 ms', 'green']],
+                                  ['1.50 sec', ['+200.00 ms', 'red']]]
+    assert list((fo('key2'))) == [['1200', ['+1', 'red']],
+                                  ['2.00 sec', ['-400.00 ms', 'green']],
+                                  ['2.00 sec', ['-400.00 ms', 'green']]]
+
+
+if __name__ == "__main__":
+    pytest.main()