exeplot 0.4.3__tar.gz → 0.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {exeplot-0.4.3 → exeplot-0.5.2}/.coveragerc +1 -0
  2. {exeplot-0.4.3 → exeplot-0.5.2}/.github/workflows/python-package.yml +1 -1
  3. {exeplot-0.4.3 → exeplot-0.5.2}/PKG-INFO +3 -3
  4. {exeplot-0.4.3 → exeplot-0.5.2}/README.md +1 -1
  5. {exeplot-0.4.3 → exeplot-0.5.2}/docs/coverage.svg +1 -1
  6. exeplot-0.5.2/docs/pages/img/upx_calc_byte.png +0 -0
  7. exeplot-0.5.2/docs/pages/img/upx_calc_entropy.png +0 -0
  8. {exeplot-0.4.3 → exeplot-0.5.2}/pyproject.toml +1 -1
  9. exeplot-0.5.2/src/exeplot/VERSION.txt +1 -0
  10. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/__conf__.py +3 -3
  11. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/__main__.py +5 -4
  12. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/utils.py +35 -14
  13. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot.egg-info/PKG-INFO +3 -3
  14. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot.egg-info/SOURCES.txt +2 -0
  15. {exeplot-0.4.3 → exeplot-0.5.2}/tests/test_others.py +6 -3
  16. exeplot-0.4.3/src/exeplot/VERSION.txt +0 -1
  17. {exeplot-0.4.3 → exeplot-0.5.2}/.gitignore +0 -0
  18. {exeplot-0.4.3 → exeplot-0.5.2}/.readthedocs.yml +0 -0
  19. {exeplot-0.4.3 → exeplot-0.5.2}/LICENSE +0 -0
  20. {exeplot-0.4.3 → exeplot-0.5.2}/_config.yml +0 -0
  21. {exeplot-0.4.3 → exeplot-0.5.2}/docs/mkdocs.yml +0 -0
  22. {exeplot-0.4.3 → exeplot-0.5.2}/docs/pages/css/extra.css +0 -0
  23. {exeplot-0.4.3 → exeplot-0.5.2}/docs/pages/img/calc_orig_entropy.png +0 -0
  24. {exeplot-0.4.3 → exeplot-0.5.2}/docs/pages/img/calc_packed_byte.png +0 -0
  25. /exeplot-0.4.3/docs/pages/img/upx_calc_byte.png → /exeplot-0.5.2/docs/pages/img/calc_packed_byte2.png +0 -0
  26. /exeplot-0.4.3/docs/pages/img/upx_calc_entropy.png → /exeplot-0.5.2/docs/pages/img/calc_packed_entropy.png +0 -0
  27. {exeplot-0.4.3 → exeplot-0.5.2}/docs/pages/img/calc_packed_nested_pie.png +0 -0
  28. {exeplot-0.4.3 → exeplot-0.5.2}/docs/pages/img/calc_packed_pie.png +0 -0
  29. {exeplot-0.4.3 → exeplot-0.5.2}/docs/pages/img/icon.png +0 -0
  30. {exeplot-0.4.3 → exeplot-0.5.2}/docs/pages/img/logo.png +0 -0
  31. {exeplot-0.4.3 → exeplot-0.5.2}/docs/pages/index.md +0 -0
  32. {exeplot-0.4.3 → exeplot-0.5.2}/docs/requirements.txt +0 -0
  33. {exeplot-0.4.3 → exeplot-0.5.2}/pytest.ini +0 -0
  34. {exeplot-0.4.3 → exeplot-0.5.2}/requirements.txt +0 -0
  35. {exeplot-0.4.3 → exeplot-0.5.2}/setup.cfg +0 -0
  36. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/__info__.py +0 -0
  37. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/__init__.py +0 -0
  38. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/plots/__common__.py +0 -0
  39. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/plots/__init__.py +0 -0
  40. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/plots/byte.py +0 -0
  41. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/plots/diff.py +0 -0
  42. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/plots/entropy.py +0 -0
  43. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/plots/graph.py +0 -0
  44. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/plots/nested_pie.py +0 -0
  45. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot/plots/pie.py +0 -0
  46. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot.egg-info/dependency_links.txt +0 -0
  47. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot.egg-info/entry_points.txt +0 -0
  48. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot.egg-info/requires.txt +0 -0
  49. {exeplot-0.4.3 → exeplot-0.5.2}/src/exeplot.egg-info/top_level.txt +0 -0
  50. {exeplot-0.4.3 → exeplot-0.5.2}/tests/__init__.py +0 -0
  51. {exeplot-0.4.3 → exeplot-0.5.2}/tests/hello.elf +0 -0
  52. {exeplot-0.4.3 → exeplot-0.5.2}/tests/hello.exe +0 -0
  53. {exeplot-0.4.3 → exeplot-0.5.2}/tests/hello.macho +0 -0
  54. {exeplot-0.4.3 → exeplot-0.5.2}/tests/test_plots.py +0 -0
@@ -21,3 +21,4 @@ exclude_lines =
21
21
  if self.type not in ["ELF", "MachO", "PE"]:
22
22
  if j <= i or start2 is None:
23
23
  if s != self.__size:
24
+ glob['_IMP'] = True
@@ -19,7 +19,7 @@ jobs:
19
19
  fail-fast: false
20
20
  matrix:
21
21
  os: [ubuntu-latest]
22
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
22
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
23
23
  steps:
24
24
  - uses: actions/checkout@v3
25
25
  - name: Set up Python ${{ matrix.python-version }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exeplot
3
- Version: 0.4.3
3
+ Version: 0.5.2
4
4
  Summary: Library for plotting executable samples supporting multiple formats
5
5
  Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -689,7 +689,7 @@ Classifier: Intended Audience :: Developers
689
689
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
690
690
  Classifier: Programming Language :: Python :: 3
691
691
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
692
- Requires-Python: <4,>=3.9
692
+ Requires-Python: <4,>=3.10
693
693
  Description-Content-Type: text/markdown
694
694
  License-File: LICENSE
695
695
  Requires-Dist: lief>=0.16.1
@@ -736,7 +736,7 @@ Draw a simplified byte plot of `calc_packed.exe`:
736
736
  $ exeplot byte calc_packed.exe --no-title --no-legend
737
737
  ```
738
738
 
739
- ![Simplified byte plot of `calc_packed.exe`](https://github.com/packing-box/python-exeplot/blob/main/docs/pages/img/calc_packed_byte.png?raw=true)
739
+ ![Simplified byte plot of `calc_packed.exe`](https://github.com/packing-box/python-exeplot/blob/main/docs/pages/img/calc_packed_byte2.png?raw=true)
740
740
 
741
741
  Draw a pie plot of `calc_packed.exe`:
742
742
 
@@ -32,7 +32,7 @@ Draw a simplified byte plot of `calc_packed.exe`:
32
32
  $ exeplot byte calc_packed.exe --no-title --no-legend
33
33
  ```
34
34
 
35
- ![Simplified byte plot of `calc_packed.exe`](https://github.com/packing-box/python-exeplot/blob/main/docs/pages/img/calc_packed_byte.png?raw=true)
35
+ ![Simplified byte plot of `calc_packed.exe`](https://github.com/packing-box/python-exeplot/blob/main/docs/pages/img/calc_packed_byte2.png?raw=true)
36
36
 
37
37
  Draw a pie plot of `calc_packed.exe`:
38
38
 
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 96.70%"><title>coverage: 96.70%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#4c1"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="865" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">96.70%</text><text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="430">96.70%</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 96.54%"><title>coverage: 96.54%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#4c1"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="865" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">96.54%</text><text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="430">96.54%</text></g></svg>
@@ -22,7 +22,7 @@ authors = [
22
22
  description = "Library for plotting executable samples supporting multiple formats"
23
23
  license = {file = "LICENSE"}
24
24
  keywords = ["python", "development", "programming", "executable-samples", "plot", "entropy", "cfg"]
25
- requires-python = ">=3.9,<4"
25
+ requires-python = ">=3.10,<4"
26
26
  classifiers = [
27
27
  "Development Status :: 5 - Production/Stable",
28
28
  "Environment :: Console",
@@ -0,0 +1 @@
1
+ 0.5.2
@@ -29,7 +29,7 @@ config = {
29
29
  numpy.int = numpy.int_ # dirty fix to "AttributeError: module 'numpy' has no attribute 'int'."
30
30
 
31
31
 
32
- def check_imports(*names):
32
+ def check_imports(*names) -> None:
33
33
  import warnings
34
34
  from inspect import currentframe
35
35
  glob = currentframe().f_back.f_globals
@@ -42,7 +42,7 @@ def check_imports(*names):
42
42
  glob['_IMP'] = False
43
43
 
44
44
 
45
- def configure(): # pragma: no cover
45
+ def configure() -> None: # pragma: no cover
46
46
  from configparser import ConfigParser
47
47
  from os.path import exists, expanduser
48
48
  path = expanduser("~/.exeplot.conf")
@@ -58,7 +58,7 @@ def configure(): # pragma: no cover
58
58
  plt.rcParams['font.family'] = config['font_family']
59
59
 
60
60
 
61
- def configure_fonts(**kw):
61
+ def configure_fonts(**kw) -> dict:
62
62
  import matplotlib
63
63
  matplotlib.rc('font', **{k.split("_")[1]: kw.pop(k, config[k]) for k in ['font_family', 'font_size']})
64
64
  kw['title-font'] = {'fontfamily': kw.pop('title_font_family', config['font_family']),
@@ -1,11 +1,12 @@
1
1
  # -*- coding: UTF-8 -*-
2
+ from argparse import ArgumentParser, Namespace, RawTextHelpFormatter
3
+
2
4
  from .__info__ import __author__, __copyright__, __email__, __license__, __source__, __version__
3
5
  from .__init__ import *
4
6
  from .__init__ import __all__ as _plots
5
7
 
6
8
 
7
- def _parser(name, description, examples):
8
- from argparse import ArgumentParser, RawTextHelpFormatter
9
+ def _parser(name: str, description: str, examples: list[str]) -> ArgumentParser:
9
10
  descr = f"{name} {__version__}\n\nAuthor : {__author__} ({__email__})\nCopyright: {__copyright__}\nLicense :" \
10
11
  f" {__license__}\nSource : {__source__}\n\n{description}.\n\n"
11
12
  examples = [f"exeplot {e}" if not e.startswith("exeplot ") else e for e in examples]
@@ -13,7 +14,7 @@ def _parser(name, description, examples):
13
14
  epilog="usage examples:\n " + "\n ".join(examples) if len(examples) > 0 else None)
14
15
 
15
16
 
16
- def _setup(parser): # pragma: no cover
17
+ def _setup(parser: ArgumentParser) -> Namespace: # pragma: no cover
17
18
  args = parser.parse_args()
18
19
  if hasattr(args, "verbose"):
19
20
  import logging
@@ -22,7 +23,7 @@ def _setup(parser): # pragma: no cover
22
23
  return args
23
24
 
24
25
 
25
- def main():
26
+ def main() -> None: # pragma: no cover
26
27
  from os import makedirs
27
28
  parser = _parser("Exeplot", "This tool allows to plot executable sample(s) in different ways",
28
29
  ["byte binary.exe", "entropy binary1.exe binary2.exe --scale"])
@@ -1,5 +1,7 @@
1
1
  # -*- coding: UTF-8 -*-
2
+ import numpy as np
2
3
  from math import log2
4
+ from typing import Optional
3
5
 
4
6
 
5
7
  __all__ = ["ensure_str", "human_readable_size", "ngrams_counts", "ngrams_distribution", "shannon_entropy"]
@@ -7,7 +9,7 @@ __all__ = ["ensure_str", "human_readable_size", "ngrams_counts", "ngrams_distrib
7
9
  shannon_entropy = lambda b: -sum([p*log2(p) for p in [float(ctr)/len(b) for ctr in [b.count(c) for c in set(b)]]]) or 0.
8
10
 
9
11
 
10
- def ensure_str(s, encoding='utf-8', errors='strict'):
12
+ def ensure_str(s: str | bytes, encoding: str = "utf-8", errors: str = "strict") -> str:
11
13
  """ Ensure that an input string is decoded. """
12
14
  if isinstance(s, bytes):
13
15
  try:
@@ -19,7 +21,7 @@ def ensure_str(s, encoding='utf-8', errors='strict'):
19
21
  return s
20
22
 
21
23
 
22
- def human_readable_size(size, precision=0):
24
+ def human_readable_size(size: int, precision: int = 0) -> str:
23
25
  """ Display bytes' size in a human-readable format given a precision. """
24
26
  i, units = 0, ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
25
27
  while size >= 1024 and i < len(units)-1:
@@ -28,27 +30,45 @@ def human_readable_size(size, precision=0):
28
30
  return "%.*f%s" % (precision, size, units[i])
29
31
 
30
32
 
31
- def ngrams_counts(byte_obj, n=1, step=1):
33
+ def ngrams_counts(byte_obj: bytes | object, n: int = 1, step: int = 1) -> dict[bytes, int]:
32
34
  """ Output the Counter instance for an input byte sequence or byte object based on n-grams.
33
35
  If the input is a byte object, cache the result.
34
36
 
35
37
  :param byte_obj: byte sequence ('bytes') or byte object with "bytes" and "size" attributes (i.e. pathlib2.Path)
36
38
  :param n: n determining the size of n-grams, defaults to 1
37
39
  :param step: step for sliding the n-grams
40
+ :param start: number of bytes to start from
38
41
  """
39
- from collections import Counter
40
- if isinstance(byte_obj, (str, bytes)):
41
- return Counter(byte_obj[i:i+n] for i in range(0, len(byte_obj)-n+1, step))
42
- elif hasattr(byte_obj, "bytes") and hasattr(byte_obj, "size"):
43
- if not hasattr(byte_obj, "_ngram_counts_cache"):
44
- byte_obj._ngram_counts_cache = {}
45
- if n not in byte_obj._ngram_counts_cache.keys():
46
- byte_obj._ngram_counts_cache[n] = Counter(byte_obj.bytes[i:i+n] for i in range(0, byte_obj.size-n+1, step))
47
- return byte_obj._ngram_counts_cache[n]
42
+ if n not in (1, 2, 3):
43
+ raise ValueError("n must be 1, 2, or 3")
44
+ if step <= 0:
45
+ raise ValueError("step must be positive")
46
+ if isinstance(byte_obj, bytes) or hasattr(byte_obj, "bytes"):
47
+ a = np.frombuffer(data := byte_obj if isinstance(byte_obj, bytes) else byte_obj.bytes, dtype=np.uint8)
48
+ l = a.size
49
+ if l < n:
50
+ return {}
51
+ if n == 1:
52
+ counts = {b.to_bytes(1, "big"): int(c) for b, c in \
53
+ enumerate(np.bincount(np.frombuffer(data, dtype=np.uint8)))}
54
+ else:
55
+ end = (m := (l - n) // step + 1) * step
56
+ grams = np.stack((a[0:end:step], a[1:1+end:step]), axis=1) if n == 2 else \
57
+ np.stack((a[0:end:step], a[1:1+end:step], a[2:2+end:step]), axis=1)
58
+ counts = {bytes(row): int(c) for row, c in zip(*np.unique(grams, axis=0, return_counts=True))}
59
+ if isinstance(byte_obj, bytes):
60
+ return counts
61
+ elif hasattr(byte_obj, "bytes"):
62
+ if not hasattr(byte_obj, "_ngram_counts_cache"):
63
+ byte_obj._ngram_counts_cache = {}
64
+ if n not in byte_obj._ngram_counts_cache.keys():
65
+ byte_obj._ngram_counts_cache[n] = counts
66
+ return byte_obj._ngram_counts_cache[n]
48
67
  raise TypeError("Bad input type ; should be a byte sequence or object")
49
68
 
50
69
 
51
- def ngrams_distribution(byte_obj, n=1, step=1, n_most_common=None, n_exclude_top=0, exclude=None):
70
+ def ngrams_distribution(byte_obj: bytes | object, n: int = 1, step: int = 1, n_most_common: Optional[int] = None,
71
+ n_exclude_top: int = 0, exclude: Optional[list] = None) -> list[tuple[bytes, int]]:
52
72
  """ Compute the n-grams distribution of an input byte sequence or byte object given exclusions.
53
73
 
54
74
  :param byte_obj: byte sequence ('bytes') or byte object with "bytes" and "size" attributes (i.e. pathlib2.Path)
@@ -60,7 +80,8 @@ def ngrams_distribution(byte_obj, n=1, step=1, n_most_common=None, n_exclude_top
60
80
  :return: list of n_most_common (n-gram, count) pairs
61
81
  """
62
82
  c = ngrams_counts(byte_obj, n, step)
63
- r = c.most_common(len(c) if n_most_common is None else n_most_common + n_exclude_top + len(exclude or []))
83
+ n = len(c) if n_most_common is None else n_most_common + n_exclude_top + len(exclude or [])
84
+ r = sorted(c.items(), key=lambda p: p[1], reverse=True)[:n]
64
85
  if exclude is not None:
65
86
  r = [(ngram, count) for ngram, count in r if ngram not in exclude]
66
87
  return r[n_exclude_top:n_exclude_top+(n_most_common or len(c))]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exeplot
3
- Version: 0.4.3
3
+ Version: 0.5.2
4
4
  Summary: Library for plotting executable samples supporting multiple formats
5
5
  Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -689,7 +689,7 @@ Classifier: Intended Audience :: Developers
689
689
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
690
690
  Classifier: Programming Language :: Python :: 3
691
691
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
692
- Requires-Python: <4,>=3.9
692
+ Requires-Python: <4,>=3.10
693
693
  Description-Content-Type: text/markdown
694
694
  License-File: LICENSE
695
695
  Requires-Dist: lief>=0.16.1
@@ -736,7 +736,7 @@ Draw a simplified byte plot of `calc_packed.exe`:
736
736
  $ exeplot byte calc_packed.exe --no-title --no-legend
737
737
  ```
738
738
 
739
- ![Simplified byte plot of `calc_packed.exe`](https://github.com/packing-box/python-exeplot/blob/main/docs/pages/img/calc_packed_byte.png?raw=true)
739
+ ![Simplified byte plot of `calc_packed.exe`](https://github.com/packing-box/python-exeplot/blob/main/docs/pages/img/calc_packed_byte2.png?raw=true)
740
740
 
741
741
  Draw a pie plot of `calc_packed.exe`:
742
742
 
@@ -15,6 +15,8 @@ docs/pages/index.md
15
15
  docs/pages/css/extra.css
16
16
  docs/pages/img/calc_orig_entropy.png
17
17
  docs/pages/img/calc_packed_byte.png
18
+ docs/pages/img/calc_packed_byte2.png
19
+ docs/pages/img/calc_packed_entropy.png
18
20
  docs/pages/img/calc_packed_nested_pie.png
19
21
  docs/pages/img/calc_packed_pie.png
20
22
  docs/pages/img/icon.png
@@ -2,7 +2,6 @@
2
2
  # -*- coding: UTF-8 -*-
3
3
  import matplotlib.pyplot as plt
4
4
  import os
5
- from collections import Counter
6
5
  from exeplot.plots.__common__ import Binary
7
6
  from exeplot.utils import *
8
7
  from unittest import TestCase
@@ -21,10 +20,14 @@ class TestOthers(TestCase):
21
20
  class TestUtils(TestCase):
22
21
  def test_ngrams_functions(self):
23
22
  self.assertRaises(TypeError, ngrams_counts, 123)
24
- self.assertTrue(isinstance(ngrams_counts(seq := b"\x00" * 4 + os.urandom(120) + b"\xff" * 4), Counter))
23
+ for n in [0, 4]:
24
+ self.assertRaises(ValueError, ngrams_counts, b"abc", n=n)
25
+ self.assertRaises(ValueError, ngrams_counts, b"abc", step=-1)
26
+ self.assertEqual(ngrams_counts(b"a", n=2), {})
27
+ self.assertTrue(isinstance(ngrams_counts(seq := b"\x00" * 4 + os.urandom(120) + b"\xff" * 4), dict))
28
+ self.assertTrue(isinstance(ngrams_counts(seq := b"\x00" * 4 + os.urandom(120) + b"\xff" * 4, n=2), dict))
25
29
  class Test:
26
30
  bytes = seq
27
- size = len(seq)
28
31
  histogram = ngrams_distribution(t := Test(), exclude=(b"\x00", b"\xff"))
29
32
  self.assertTrue(isinstance(histogram, list))
30
33
  self.assertNotIn(b"\x00", [b for b, c in histogram])
@@ -1 +0,0 @@
1
- 0.4.3
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes