From f5ab1a35d1dcfb50b37b6f9b5e055793aeec44d5 Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Wed, 6 Apr 2022 07:04:59 +0200 Subject: [PATCH] DEV: Add Github Action for testing (#660) Also add .pre-commit-config.yaml --- .coveragerc | 23 ++++++++++++++++ .github/workflows/unit-tests.yaml | 36 +++++++++++++++++++++++++ .gitignore | 2 +- .pre-commit-config.yaml | 39 +++++++++++++++++++++++++++ PyPDF2/generic.py | 8 +++--- PyPDF2/merger.py | 2 +- PyPDF2/pdf.py | 28 ++++++++++---------- requirements/ci.in | 4 +++ requirements/ci.txt | 44 +++++++++++++++++++++++++++++++ 9 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 .coveragerc create mode 100644 .github/workflows/unit-tests.yaml create mode 100644 .pre-commit-config.yaml create mode 100644 requirements/ci.in create mode 100644 requirements/ci.txt diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..26b134595 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,23 @@ +[run] +source = PyPDF2 +branch = True + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + @overload + + # Don't complain about missing debug-only code: + def __repr__ + def __str__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: \ No newline at end of file diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml new file mode 100644 index 000000000..e5b20850f --- /dev/null +++ b/.github/workflows/unit-tests.yaml @@ -0,0 +1,36 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Unit Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements/ci.txt + pip install . + - name: Test with flake8 + run: | + flake8 . --ignore E,F,I,SIM,C,PT,N,ASS,A,P,R,W + - name: Test with pytest + run: | + pytest Tests/tests.py --cov --cov-report term-missing -vv diff --git a/.gitignore b/.gitignore index 70b74978f..ab7a48903 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ .tox build .idea/* - +.coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..f76cfebf9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,39 @@ +# pre-commit run --all-files +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: check-ast + - id: check-byte-order-marker + - id: check-case-conflict + - id: check-docstring-first + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending + - id: check-added-large-files + args: ['--maxkb=1000'] +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + args: ["--ignore", "E,W,F"] +# - repo: https://github.com/pre-commit/mirrors-mypy +# rev: v0.942 +# hooks: +# - id: mypy +# - repo: https://github.com/psf/black +# rev: 22.3.0 +# hooks: +# - id: black +# - repo: https://github.com/asottile/pyupgrade +# rev: v2.31.1 +# hooks: +# - id: pyupgrade +# args: [--py36-plus] +- repo: https://github.com/asottile/blacken-docs + rev: v1.12.1 + hooks: + - id: blacken-docs + additional_dependencies: [black==22.1.0] diff --git a/PyPDF2/generic.py b/PyPDF2/generic.py index 959957dde..b1f7a1114 100644 --- a/PyPDF2/generic.py +++ b/PyPDF2/generic.py @@ -228,7 +228,7 @@ class FloatObject(decimal.Decimal, PdfObject): def __new__(cls, value="0", context=None): try: return decimal.Decimal.__new__(cls, utils.str_(value), context) - except: + except Exception: return decimal.Decimal.__new__(cls, str(value)) def __repr__(self): @@ -355,7 +355,7 @@ def readStringFromStream(stream): b_("%") : b_("%"), b_("<") : b_("<"), b_(">") : b_(">"), - b_("[") : b_("["), + b_("[") : b_("["), b_("]") : b_("]"), b_("#") : b_("#"), b_("_") : b_("_"), @@ -371,7 +371,7 @@ def readStringFromStream(stream): # Three octal digits shall be used, with leading zeros # as needed, if the next character of the string is also # a digit." (PDF reference 7.3.4.2, p 16) - for i in range(2): + for _ in range(2): ntok = stream.read(1) if ntok.isdigit(): tok += ntok @@ -479,7 +479,7 @@ def readFromStream(stream, pdf): name = stream.read(1) if name != NameObject.surfix: raise utils.PdfReadError("name read error") - name += utils.readUntilRegex(stream, NameObject.delimiterPattern, + name += utils.readUntilRegex(stream, NameObject.delimiterPattern, ignore_eof=True) if debug: print(name) try: diff --git a/PyPDF2/merger.py b/PyPDF2/merger.py index c3373e445..138d55cdc 100644 --- a/PyPDF2/merger.py +++ b/PyPDF2/merger.py @@ -238,7 +238,7 @@ def close(self): usage. """ self.pages = [] - for fo, pdfr, mine in self.inputs: + for fo, _pdfr, mine in self.inputs: if mine: fo.close() diff --git a/PyPDF2/pdf.py b/PyPDF2/pdf.py index 1e1bcc7e9..d278a874e 100644 --- a/PyPDF2/pdf.py +++ b/PyPDF2/pdf.py @@ -250,17 +250,17 @@ def addAttachment(self, fname, fdata): :param str fname: The filename to display. :param str fdata: The data in the file. - + Reference: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf Section 7.11.3 """ - + # We need 3 entries: # * The file's data # * The /Filespec entry # * The file's name, which goes in the Catalog - + # The entry for the file """ Sample: @@ -272,7 +272,7 @@ def addAttachment(self, fname, fdata): stream Hello world! endstream - endobj + endobj """ file_entry = DecodedStreamObject() file_entry.setData(fdata) @@ -291,14 +291,14 @@ def addAttachment(self, fname, fdata): """ efEntry = DictionaryObject() efEntry.update({ NameObject("/F"):file_entry }) - + filespec = DictionaryObject() filespec.update({ NameObject("/Type"): NameObject("/Filespec"), NameObject("/F"): createStringObject(fname), # Perhaps also try TextStringObject NameObject("/EF"): efEntry }) - + # Then create the entry for the root, as it needs a reference to the Filespec """ Sample: 1 0 obj @@ -309,13 +309,13 @@ def addAttachment(self, fname, fdata): /Names << /EmbeddedFiles << /Names [(hello.txt) 7 0 R] >> >> >> endobj - + """ embeddedFilesNamesDictionary = DictionaryObject() embeddedFilesNamesDictionary.update({ NameObject("/Names"): ArrayObject([createStringObject(fname), filespec]) }) - + embeddedFilesDictionary = DictionaryObject() embeddedFilesDictionary.update({ NameObject("/EmbeddedFiles"): embeddedFilesNamesDictionary @@ -329,7 +329,7 @@ def appendPagesFromReader(self, reader, after_page_append=None): """ Copy pages from reader to writer. Includes an optional callback parameter which is invoked after pages are appended to the writer. - + :param reader: a PdfFileReader object from which to copy page annotations to this writer object. The writer's annots will then be updated @@ -373,7 +373,7 @@ def updatePageFormFieldValues(self, page, fields): def cloneReaderDocumentRoot(self, reader): ''' Copy the reader document root to the writer. - + :param reader: PdfFileReader from the document root should be copied. :callback after_page_append ''' @@ -1210,7 +1210,7 @@ def getNumPages(self): self._override_encryption = True self.decrypt('') return self.trailer["/Root"]["/Pages"]["/Count"] - except: + except Exception: raise utils.PdfReadError("File has not been decrypted") finally: self._override_encryption = False @@ -2250,7 +2250,7 @@ def _contentStreamRename(stream, rename, pdf): if not rename: return stream stream = ContentStream(stream, pdf) - for operands, operator in stream.operations: + for operands, _operator in stream.operations: for i in range(len(operands)): op = operands[i] if isinstance(op, NameObject): @@ -2957,7 +2957,7 @@ def _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encr # encryption key as defined by the value of the encryption dictionary's # /Length entry. if rev >= 3: - for i in range(50): + for _ in range(50): md5_hash = md5(md5_hash[:keylen]).digest() # 9. Set the encryption key to the first n bytes of the output from the # final MD5 hash, where n is always 5 for revision 2 but, for revision 3 or @@ -3007,7 +3007,7 @@ def _alg33_1(password, rev, keylen): # from the previous MD5 hash and pass it as input into a new MD5 hash. md5_hash = m.digest() if rev >= 3: - for i in range(50): + for _ in range(50): md5_hash = md5(md5_hash).digest() # 4. Create an RC4 encryption key using the first n bytes of the output # from the final MD5 hash, where n is always 5 for revision 2 but, for diff --git a/requirements/ci.in b/requirements/ci.in new file mode 100644 index 000000000..2fb67237b --- /dev/null +++ b/requirements/ci.in @@ -0,0 +1,4 @@ +pytest +flake8 +flake8-bugbear +pytest-cov \ No newline at end of file diff --git a/requirements/ci.txt b/requirements/ci.txt new file mode 100644 index 000000000..fa03f3956 --- /dev/null +++ b/requirements/ci.txt @@ -0,0 +1,44 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile ci.in +# +attrs==21.4.0 + # via + # flake8-bugbear + # pytest +coverage[toml]==6.3.2 + # via pytest-cov +flake8==4.0.1 + # via + # -r ci.in + # flake8-bugbear +flake8-bugbear==22.3.23 + # via -r ci.in +iniconfig==1.1.1 + # via pytest +mccabe==0.6.1 + # via flake8 +packaging==21.3 + # via pytest +pluggy==1.0.0 + # via pytest +py==1.11.0 + # via pytest +pycodestyle==2.8.0 + # via flake8 +pyflakes==2.4.0 + # via flake8 +pyparsing==3.0.7 + # via packaging +pytest==7.1.1 + # via + # -r ci.in + # pytest-cov +pytest-cov==3.0.0 + # via -r ci.in +tomli==2.0.1 + # via + # coverage + # pytest