Repository: spulec/freezegun

Issue:
Freezegun does not unfreeze `time.time_ns()`
Currently when Freezegun stops freezing time `time.time_ns()` is still faked. This leads to the following exception on the next call to `time.time_ns()`

```
/src # git rev-parse --verify HEAD
f70b77c0867fe3bb78cb58570ece031d7fdd34e7
/src # git status
On branch master
Your branch is up to date with 'origin/master'.


/src # python
Python 3.7.4 (default, Jul 13 2019, 14:44:04) 
[GCC 8.3.0] on linux
>>> import time
>>> time.time(); time.time_ns()
1571353599.7948837
1571353599795022000
>>> from freezegun import freeze_time
>>> freezer = freeze_time('1970-01-01')
>>> time.time(); time.time_ns()
1571353634.7175064
1571353634717627500
>>> freezer.start()
<freezegun.api.FrozenDateTimeFactory object at 0x7f399bc4d8d0>
>>> time.time(); time.time_ns()
0.0
0
>>> freezer.stop()
>>> time.time()
1571353657.3422928
>>> time.time_ns()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/src/freezegun/api.py", line 186, in fake_time_ns
    if _should_use_real_time():
  File "/src/freezegun/api.py", line 157, in _should_use_real_time
    if not ignore_lists[-1]:
IndexError: list index out of range
```

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/26287741/badge)](https://coveralls.io/builds/26287741)

Coverage increased (+0.1%) to 91.793% when pulling **a35fe80ccc783c577b4f084feae1bcfc220d9a1f on eric-weaver:issue-312-unfreeze-time-ns** into **f70b77c0867fe3bb78cb58570ece031d7fdd34e7 on spulec:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/26287741/badge)](https://coveralls.io/builds/26287741)

Coverage increased (+0.1%) to 91.793% when pulling **a35fe80ccc783c577b4f084feae1bcfc220d9a1f on eric-weaver:issue-312-unfreeze-time-ns** into **f70b77c0867fe3bb78cb58570ece031d7fdd34e7 on spulec:master**.

boxed: Does this work when starting freezegun again? 


Also it seems weird that this specific function is handled very differently from the others. There we just pass through if not frozen. 
eric-weaver: @boxed yes this looks to be working when freezing again. Below are examples tested against python 3.7.4. 

The first one is from current [master](https://github.com/spulec/freezegun/tree/f70b77c0867fe3bb78cb58570ece031d7fdd34e7)
```
/src # git rev-parse --verify HEAD
f70b77c0867fe3bb78cb58570ece031d7fdd34e7
/src # git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean
/src # python
Python 3.7.4 (default, Jul 13 2019, 14:44:04) 
[GCC 8.3.0] on linux
>>> import time
>>> time.time(); time.time_ns()
1571353599.7948837
1571353599795022000
>>> from freezegun import freeze_time
>>> freezer = freeze_time('1970-01-01')
>>> time.time(); time.time_ns()
1571353634.7175064
1571353634717627500
>>> freezer.start()
<freezegun.api.FrozenDateTimeFactory object at 0x7f399bc4d8d0>
>>> time.time(); time.time_ns()
0.0
0
>>> freezer.stop()
>>> time.time()
1571353657.3422928
>>> time.time_ns()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/src/freezegun/api.py", line 186, in fake_time_ns
    if _should_use_real_time():
  File "/src/freezegun/api.py", line 157, in _should_use_real_time
    if not ignore_lists[-1]:
IndexError: list index out of range
```

And this is after the changes in this pull request
```
/src # git rev-parse --verify HEAD
a35fe80ccc783c577b4f084feae1bcfc220d9a1f
/src # git status
On branch issue-312-unfreeze-time-ns
Your branch is up to date with 'origin/issue-312-unfreeze-time-ns'.

nothing to commit, working tree clean
/src # python
Python 3.7.4 (default, Jul 13 2019, 14:44:04) 
[GCC 8.3.0] on linux
>>> import time
>>> time.time();time.time_ns()
1571354000.6646214
1571354000664727500
>>> from freezegun import freeze_time
>>> freezer = freeze_time('1970-01-01')
>>> time.time();time.time_ns()
1571354021.676702
1571354021676739800
>>> freezer.start()
<freezegun.api.FrozenDateTimeFactory object at 0x7fe225308dd0>
>>> time.time();time.time_ns()
0.0
0
>>> freezer.stop()
>>> time.time();time.time_ns()
1571354042.633977
1571354042634079200
>>> freezer.start()
<freezegun.api.FrozenDateTimeFactory object at 0x7fe22571f350>
>>> time.time();time.time_ns()
0.0
0
>>> freezer.stop()
>>> time.time();time.time_ns()
1571354063.045664
1571354063045720100
>>> freezer = freeze_time('2000-01-01')
>>> freezer.start()
<freezegun.api.FrozenDateTimeFactory object at 0x7fe225318850>
>>> time.time();time.time_ns()
946684800.0
946684800000000000
>>> freezer.stop()
>>> time.time();time.time_ns()
1571354090.9213243
1571354090921366600
```
eric-weaver: Also I agree that the implementation of freezing `time_ns` is different than everything else in this module. One reason I presume is because this function was only introduced in python 3.7. I'd be happy to take a stab at cleaning it up either in this PR or another if there is a better pattern we wish to follow here. [clock](https://github.com/spulec/freezegun/blob/011138dc548bcf77c237a9f4f334b2838aa9d10b/freezegun/api.py#L41) is another function that needs to be implemented special as it's removed in 3.8. I also noticed in #277 there's reference to adding `time.perf_counter` which was only introduced in python 3.3 and it has a sibling `time.perf_counter_ns` introduced in 3.7. I point these out as there is a lot being added/removed from the time module in recent versions. Would be interested in any thoughts on how to standardize compatibility between them in freezegun
boxed: Maybe we should just pull in these fixes for low and clean up later. It's often easier to see the pattern after a few cases. 
boxed: There's conflicts on this PR now btw. 

Patch:
diff --git a/freezegun/api.py b/freezegun/api.py
index b32e3ed..667c657 100644
--- a/freezegun/api.py
+++ b/freezegun/api.py
@@ -709,6 +709,9 @@ class _freeze_time(object):
             time.strftime = real_strftime
             time.clock = real_clock
 
+            if _TIME_NS_PRESENT:
+                time.time_ns = real_time_ns
+
             if uuid_generate_time_attr:
                 setattr(uuid, uuid_generate_time_attr, real_uuid_generate_time)
             uuid._UuidCreate = real_uuid_create


Test patch:
diff --git a/tests/test_datetimes.py b/tests/test_datetimes.py
index a431104..a5f0b93 100644
--- a/tests/test_datetimes.py
+++ b/tests/test_datetimes.py
@@ -690,6 +690,7 @@ def test_time_ns():
     assert time.time_ns() == expected_timestamp * 1e9
     freezer.stop()
     assert time.time() != expected_timestamp
+    assert time.time_ns() != expected_timestamp * 1e9
 
 
 @pytest.mark.skip("timezone handling is currently incorrect")