Repository: bloomberg/pytest-memray (language: python)

Issue:
Running pytest with --most-allocations throws an ZeroDivisionError: float divmod()
## Bug Report

**Current Behavior** A clear and concise description of the behavior.

Because of #46 not being resolved, I'm using a bash command to get the number of tests and passing it to `--most-allocations`. Some of the test cases are skipped, however.

**Input Code**

In my GitHub Actions workflow:

1. Save number of tests to an environment variable:
```
echo "NUM_TESTS=$(python3 -m pytest new_tests/ --collect-only -q | tail -n 1 | awk '{print $1;}')" >> $GITHUB_ENV
```

2. Run pytest with --most-allocations equal to number of tests:
```
Run python -m pytest ./new_tests --memray --memray-bin-path=./ --most-allocations=${{ env.NUM_TESTS }}
```

Full workflow run and code: https://github.com/aerospike/aerospike-client-python/actions/runs/6276247630/job/17045554326

**Expected behavior/code** A clear and concise description of what you expected to
happen (or code).

pytest should generate and print out a report of the memory allocations for all the tests without throwing an error.

Although it successfully generates a report for all the tests, it throws this error:

```
2023-09-22T15:46:56.1106515Z Traceback (most recent call last):
2023-09-22T15:46:56.1113560Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/runpy.py", line 194, in _run_module_as_main
2023-09-22T15:46:56.1114186Z     return _run_code(code, main_globals, None,
2023-09-22T15:46:56.1114465Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/runpy.py", line 87, in _run_code
2023-09-22T15:46:56.1114587Z     exec(code, run_globals)
2023-09-22T15:46:56.1115000Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pytest/__main__.py", line 5, in <module>
2023-09-22T15:46:56.1115161Z     raise SystemExit(pytest.console_main())
2023-09-22T15:46:56.1115530Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/config/__init__.py", line 189, in console_main
2023-09-22T15:46:56.1115628Z     code = main()
2023-09-22T15:46:56.1115969Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/config/__init__.py", line 166, in main
2023-09-22T15:46:56.1116141Z     ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
2023-09-22T15:46:56.1116480Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_hooks.py", line 493, in __call__
2023-09-22T15:46:56.1116664Z     return self._hookexec(self.name, self._hookimpls, kwargs, firstresult)
2023-09-22T15:46:56.1117006Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_manager.py", line 115, in _hookexec
2023-09-22T15:46:56.1117174Z     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
2023-09-22T15:46:56.1117515Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py", line 113, in _multicall
2023-09-22T15:46:56.1117704Z     raise exception.with_traceback(exception.__traceback__)
2023-09-22T15:46:56.1118050Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py", line 77, in _multicall
2023-09-22T15:46:56.1118157Z     res = hook_impl.function(*args)
2023-09-22T15:46:56.1118512Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/main.py", line 317, in pytest_cmdline_main
2023-09-22T15:46:56.1118630Z     return wrap_session(config, _main)
2023-09-22T15:46:56.1118977Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/main.py", line 305, in wrap_session
2023-09-22T15:46:56.1119102Z     config.hook.pytest_sessionfinish(
2023-09-22T15:46:56.1119432Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_hooks.py", line 493, in __call__
2023-09-22T15:46:56.1119602Z     return self._hookexec(self.name, self._hookimpls, kwargs, firstresult)
2023-09-22T15:46:56.1119939Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_manager.py", line 115, in _hookexec
2023-09-22T15:46:56.1120267Z     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
2023-09-22T15:46:56.1120637Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py", line 130, in _multicall
2023-09-22T15:46:56.1120893Z     teardown[0].send(outcome)
2023-09-22T15:46:56.1121272Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/terminal.py", line 867, in pytest_sessionfinish
2023-09-22T15:46:56.1121514Z     self.config.hook.pytest_terminal_summary(
2023-09-22T15:46:56.1121858Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_hooks.py", line 493, in __call__
2023-09-22T15:46:56.1122029Z     return self._hookexec(self.name, self._hookimpls, kwargs, firstresult)
2023-09-22T15:46:56.1122368Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_manager.py", line 115, in _hookexec
2023-09-22T15:46:56.1122523Z     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
2023-09-22T15:46:56.1122930Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py", line 152, in _multicall
2023-09-22T15:46:56.1123041Z     return outcome.get_result()
2023-09-22T15:46:56.1123374Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_result.py", line 114, in get_result
2023-09-22T15:46:56.1123528Z     raise exc.with_traceback(exc.__traceback__)
2023-09-22T15:46:56.1123866Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py", line 77, in _multicall
2023-09-22T15:46:56.1123977Z     res = hook_impl.function(*args)
2023-09-22T15:46:56.1124360Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pytest_memray/plugin.py", line 298, in pytest_terminal_summary
2023-09-22T15:46:56.1124470Z     self._report_records_for_test(
2023-09-22T15:46:56.1124858Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pytest_memray/plugin.py", line 322, in _report_records_for_test
2023-09-22T15:46:56.1125032Z     histogram_txt = cli_hist(sizes, bins=min(len(sizes), N_HISTOGRAM_BINS))
2023-09-22T15:46:56.1125377Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pytest_memray/plugin.py", line 92, in cli_hist
2023-09-22T15:46:56.1125531Z     data_bins = histogram(data, low=low, high=high, bins=bins)
2023-09-22T15:46:56.1125883Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pytest_memray/plugin.py", line 80, in histogram
2023-09-22T15:46:56.1126115Z     dist = collections.Counter((x - low) // step for x in iterable)
2023-09-22T15:46:56.1126341Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/collections/__init__.py", line 552, in __init__
2023-09-22T15:46:56.1126452Z     self.update(iterable, **kwds)
2023-09-22T15:46:56.1126669Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/collections/__init__.py", line 637, in update
2023-09-22T15:46:56.1126780Z     _count_elements(self, iterable)
2023-09-22T15:46:56.1127132Z   File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pytest_memray/plugin.py", line 80, in <genexpr>
2023-09-22T15:46:56.1127365Z     dist = collections.Counter((x - low) // step for x in iterable)
2023-09-22T15:46:56.1127478Z ZeroDivisionError: float divmod()
```

**Environment**

- Python(s): Cpython 3.8, Ubuntu 22.04

**Possible Solution**

<!--- Only if you have suggestions on a fix for the bug -->

**Additional context/Screenshots** Add any other context about the problem here. If
applicable, add screenshots to help explain.



Discussion / hints:

Fix patch:
diff --git a/docs/news/113.bugfix.rst b/docs/news/113.bugfix.rst
new file mode 100644
index 0000000..c2dab96
--- /dev/null
+++ b/docs/news/113.bugfix.rst
@@ -0,0 +1,1 @@
+Fix the generation of histograms when the tests performed zero-byte allocations.


Test patch:
diff --git a/src/pytest_memray/plugin.py b/src/pytest_memray/plugin.py
index c1f7d3d..aa81e65 100644
--- a/src/pytest_memray/plugin.py
+++ b/src/pytest_memray/plugin.py
@@ -78,7 +78,7 @@ def histogram(
     [0, 0, 0, 0, 1, 0, 0, 1, 3, 2]
 
     """
-    step = ((high - low) / bins) or low
+    step = ((high - low) / bins) or low or 1
     dist = collections.Counter((x - low) // step for x in iterable)
     return [dist[b] for b in range(bins)]
 
@@ -91,7 +91,7 @@ def cli_hist(data: Iterable[float], bins: int, *, log_scale: bool = True) -> str
     high = max(data)
     data_bins = histogram(data, low=low, high=high, bins=bins)
     bar_indexes = (int(elem * (len(bars) - 1) / max(data_bins)) for elem in data_bins)
-    result = " ".join(bars[bar_index] for bar_index in bar_indexes)
+    result = "".join(bars[bar_index] for bar_index in bar_indexes)
     return result
 
 
diff --git a/tests/test_utils.py b/tests/test_utils.py
index f3f55d9..a33c4f0 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -15,6 +15,7 @@
 
 from pytest_memray.utils import WriteEnabledDirectoryAction
 from pytest_memray.utils import parse_memory_string
+from pytest_memray.plugin import cli_hist
 
 
 @pytest.mark.parametrize(
@@ -123,3 +124,25 @@ def test_write_enabled_dir_cannot_create(
             w_dir_check(path)
     finally:
         tmp_path.chmod(tmp_path.stat().st_mode | write)
+
+
+def test_histogram_with_zero_byte_allocations():
+    # GIVEN
+    allocations = [0, 100, 990, 1000, 50000]
+
+    # WHEN
+    histogram = cli_hist(allocations, bins=5)
+
+    # THEN
+    assert histogram == "▄ ▄█▄"
+
+
+def test_histogram_with_only_zero_byte_allocations():
+    # GIVEN
+    allocations = [0, 0, 0, 0]
+
+    # WHEN
+    histogram = cli_hist(allocations, bins=5)
+
+    # THEN
+    assert histogram == "█    "