From 8afb3ff1ef021e4f31d00889a610b1d1467ed2e3 Mon Sep 17 00:00:00 2001 From: James Estevez Date: Tue, 17 Jan 2023 12:22:48 -0800 Subject: [PATCH 1/5] End-to-end release automation (#3476) * Clean up .gitignore I had to add some items to this to for our new tooling, so I went ahead and cleaned this up. I've removed some parts that were obviously out of date (`pybuild`) or duplicative, but most items were simply grouped into rough categories and sorted. * End-to-end release automation Un-pin our dependencies ----------------------- We've reached consensus that using pip-tools to pin our direct and transitive dependencies has been problematic. We often encounter dependency conflicts between our first-party (`snowfakery`), third-party, and downstream dependencies. Moreover, it also results in additional churn from Dependabot and delays in mitigating vulnerabilities. Use Hatch as build system ------------------------- Highlights: - Replaces setuptools and twine. - Removes `MANIFEST.in`, `setup.{cfg,py}` - Replaces `version.txt` with `__about__.py` for declarative version bumping - Preserves pip-tools for dependency pinning to preserve users' existing workflows. - Explicitly adds the `--resolver=backtracking` flag to pip-tools - Adds `hatch-fancy-pypi-readme` to handle PyPI `long_description` We adopted `pyproject.toml` for tool configuration a while back, but we never adopted it for package metadata, build system, or dependency specifications (see [PEP 517], [PEP 518], and [PEP 621]). Hatch is a "modern, extensible Python project manager." Unlike Poetry, it uses standard PEP 621 dependency specifications, and strict adherence to the packaging PEPs is a stated goal of the project.[^1] Because the PyPA rejected the reproducible lockfile PEP, Hatch has not yet implemented dependency resolution and locking. We've left pip-tools in place to cover this gap. Release automation workflows ---------------------------- Adds the pre-release.yaml workflow that, when passed a version bump: 1. Bumps the project version using `hatch version` 2. Automatically generate release notes, using GitHub's REST API (`releases/generate-notes`) 3. Inserts the release notes into docs/history.md 4. Creates a branch and opens a pull request against `main` Re-writes the release.yml workflow to: 1. Use `hatch` to build the package 2. Publishes the package to PyPI, concatenating the latest release notes and the README in the project description 3. Creates a GitHub release (and corresponding tag), using the latest release notes as the release body. [^1]: The full rationale for picking Hatch will be detailed in a forthcoming ADR. [PEP 517]: https://peps.python.org/pep-0517/ [PEP 518]: https://peps.python.org/pep-0518/ [PEP 621]: https://peps.python.org/pep-0621/ --- .github/workflows/pre-release.yml | 67 +++++++ .github/workflows/release.yml | 35 ++-- .github/workflows/release_test.yml | 5 +- .gitignore | 108 +++++++---- CONTRIBUTING.md | 1 + CONTRIBUTING.rst | 1 - MANIFEST.in | 16 -- Makefile | 16 +- README.md | 2 + cumulusci/__about__.py | 1 + cumulusci/__init__.py | 5 +- cumulusci/version.txt | 1 - docs/history.md | 4 + pyproject.toml | 137 ++++++++++++++ requirements/dev.txt | 285 +++++++++++++++++------------ setup.cfg | 5 - setup.py | 74 -------- utility/update-history.py | 15 ++ 18 files changed, 503 insertions(+), 275 deletions(-) create mode 100644 .github/workflows/pre-release.yml create mode 100644 CONTRIBUTING.md delete mode 100644 CONTRIBUTING.rst delete mode 100644 MANIFEST.in create mode 100644 cumulusci/__about__.py delete mode 100644 cumulusci/version.txt delete mode 100644 setup.cfg delete mode 100644 setup.py create mode 100644 utility/update-history.py diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 0000000000..43f91a1e9c --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,67 @@ +name: Draft release pull request + +on: + workflow_dispatch: + inputs: + version: + description: "Select version bump" + default: "minor" + type: choice + options: + - major + - minor + - patch + - alpha + - beta + - preview + - dev + +jobs: + generate-changelog: + name: Create a PR to update version and release notes + runs-on: sfdc-ubuntu-latest + steps: + - uses: actions/checkout@main + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + cache: pip + - name: Install build tool + run: python -m pip install hatch + - name: Bump version + run: hatch version $VERSION + env: + VERSION: ${{ inputs.version }} + - name: Generate release notes + id: changelog + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PREVIOUS_VERSION=$(gh release view --json tagName --jq .tagName) + NEXT_VERSION="v$(hatch version)" + echo "## $NEXT_VERSION ($(date -I))" > changelog.md + gh api \ + --method POST \ + -H "Accept: application/vnd.github.v3+json" \ + /repos/SFDO-Tooling/CumulusCI/releases/generate-notes \ + -f previous_tag_name=$PREVIOUS_VERSION \ + -f target_commitish='main' \ + -f tag_name=$NEXT_VERSION \ + --jq '.body' | + sed -e 's_\(https.*\/\)\([0-9]*\)$_[#\2](\1\2)_' \ + -e 's_by @\(.*\) in_by [@\1](https://github.com/\1) in_' >> changelog.md + python utility/update-history.py + - name: Commit changes + run: | + git config user.name github-actions + git config user.email github-actions@github.com + git switch -c "release-$(hatch version)" + git add docs/history.md cumulusci/__about__.py + git commit -m "Update changelog (automated)" + git push origin "release-$(hatch version)" + - name: Commit changes and open PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr create --title "Release v$(hatch version)" --fill --label 'auto-pr' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7630ffaf99..1ea5483702 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,13 @@ -name: Release cumulusci +name: Publish and release CumulusCI on: push: branches: - main paths: - - cumulusci/version.txt + - cumulusci/__about__.py + +concurrency: publishing jobs: publish-to-pypi: @@ -17,20 +19,25 @@ jobs: uses: actions/setup-python@v4 with: python-version: 3.8 + cache: pip - name: Install build tools - run: python -m pip install twine wheel + run: python -m pip install hatch - name: Build source tarball and binary wheel - run: python setup.py sdist bdist_wheel + run: hatch build -c - name: Upload to PyPI - run: twine upload dist/* + run: hatch publish + env: + HATCH_INDEX_USER: "__token__" + HATCH_INDEX_AUTH: ${{ secrets.PYPI_TOKEN }} + HATCH_INDEX_REPO: "https://test.pypi.org/legacy/" + - name: Create release env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - - name: Create tag + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - curl -s -X POST https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -d @- << EOF - {"ref": "refs/tags/v$(python setup.py --version)", "sha": "$GITHUB_SHA"} - EOF - -# to do: -# - add release notes in github + VERSION="$(hatch version)" + awk '//,//' docs/history.md > changelog.md + gh release create "v$VERSION" \ + dist/*.whl \ + dist/*.tar.gz \ + --notes-file changelog.md \ + --title $VERSION diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index ba8d851f15..104a327f99 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -47,11 +47,10 @@ jobs: - name: Install Python dependencies run: pip install -r requirements_dev.txt - name: Install build tools - run: pip install wheel + run: pip install hatch - name: Test source tarball and binary wheel run: | - python setup.py sdist bdist_wheel - twine check dist/* + hatch build - name: Install sfdx run: | mkdir sfdx diff --git a/.gitignore b/.gitignore index f6ad1a69df..d91ee2ee9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,83 @@ -*.py[cod] -.*.swp -.*.swo -venv/ +# OS cruft .DS_Store -cli/help.txt -cli/help.md -cli/help_clean.md -.swp -pybuild -cumulusci.egg-info -build/ -dist/ + +# CumulusCI config and output +.cci docs/api/ docs/_build/ -docs/tasks.rst -docs/flows.rst -.eggs/ -.idea/ -.tox/ -tags -.vscode/ +github_release_notes.html +tmp + +# Distribution / packaging +*.egg +*.egg-info/ +.Python +.installed.cfg +develop-eggs/ +dist/ +eggs/ +env/ +lib/ +lib64/ +parts/ +pip-wheel-metadata +sdist/ +var/ + +# Miscellaneous Python cruft +.pdbrc +.pdbrc.py +.python-version +venv/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*.pkl + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports .cache/ .coverage .coverage.* -.python-version -robot/CumulusCI/results/ -*.webm -playwright-log.txt +.mypy_cache/ +.pytest_cache/ +.tox/ +cov.xml +htmlcov/ +large_cassettes/ +.noseids +nosetests.xml +pytestdebug.log +results_junit.xml +test_results.json log.html report.html output.xml + +# Robot +*.webm +.pabotsuitenames geckodriver*.log +pabot_results/ +playwright-log.txt +robot/CumulusCI/results/ + +# Salesforce **/.sfdx -htmlcov/ -.pytest_cache/ -pip-wheel-metadata -.mypy_cache/ -/docs/_build +.sf + +# direnv +.direnv +.envrc + +# Editors *~ -.cci -results_junit.xml -test_results.json -pabot_results/ -.pabotsuitenames -large_cassettes/ -github_release_notes.html -tmp -pytestdebug.log +.*.swp +.*.swo +.vscode/ +.idea/ +tags diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..7c9775d9b5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +See docs/contributing.md diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index e5304ce2e4..0000000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1 +0,0 @@ -See docs/contributing.rst diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index a1a8831588..0000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,16 +0,0 @@ - -include AUTHORS.rst - -include CONTRIBUTING.rst -include docs/history.md -include LICENSE -include README.md -include requirements/prod.txt -include requirements/dev.txt - -recursive-include tests * -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] - -recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif -recursive-include cumulusci *.xml *.jar *.xsl *.txt *.properties *.sh *.py *.yml *.report *.md *.robot *.json *.css *.html dot-gitignore *.js diff --git a/Makefile b/Makefile index 73bfc77a35..de2ded5bd2 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,6 @@ slow_tests: vcr # remake VCR cassettes and run other integration tests cci org scratch_delete pytest pytest integration_tests/ --org pytest -rs - docs: ## generate Sphinx HTML documentation $(MAKE) -C docs clean $(MAKE) -C docs html @@ -83,24 +82,23 @@ servedocs: docs ## compile the docs watching for changes watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . release: clean ## package and upload a release - python setup.py sdist - python setup.py bdist_wheel - twine upload dist/* + hatch build + hatch publish dist: clean ## builds source and wheel package - python setup.py sdist bdist_wheel + hatch build ls -l dist install: clean ## install the package to the active Python's site-packages - python setup.py install + python -m pip install . tag: clean - git tag -a -m 'version $$(python setup.py --version)' v$$(python setup.py --version) + git tag -a -m 'version $$(hatch version)' v$$(hatch version) git push --follow-tags update-deps: - pip-compile --upgrade requirements/prod.in - pip-compile --upgrade requirements/dev.in + pip-compile --upgrade --resolver=backtracking --output-file=requirements/prod.txt pyproject.toml + pip-compile --upgrade --resolver=backtracking --output-file=requirements/dev.txt --all-extras pyproject.toml dev-install: python -m pip install --upgrade pip pip-tools setuptools diff --git a/README.md b/README.md index a17bb28f58..0cd1ffe3fb 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,5 @@ Community](https://success.salesforce.com/_ui/core/chatter/groups/GroupProfilePa _Please note:_ CumulusCI is distributed under an [open source license](https://github.com/SFDO-Tooling/CumulusCI/blob/main/LICENSE) and is not covered by the Salesforce Master Subscription Agreement. + + diff --git a/cumulusci/__about__.py b/cumulusci/__about__.py new file mode 100644 index 0000000000..b02e77d7ea --- /dev/null +++ b/cumulusci/__about__.py @@ -0,0 +1 @@ +__version__ = "3.78.0" diff --git a/cumulusci/__init__.py b/cumulusci/__init__.py index e013f0f533..2dd9be88c7 100644 --- a/cumulusci/__init__.py +++ b/cumulusci/__init__.py @@ -3,12 +3,13 @@ from simple_salesforce import api, bulk +from cumulusci.__about__ import __version__ + __import__("pkg_resources").declare_namespace("cumulusci") __location__ = os.path.dirname(os.path.realpath(__file__)) -with open(os.path.join(__location__, "version.txt")) as f: - __version__ = f.read().strip() +__version__ = __version__ if sys.version_info < (3, 8): # pragma: no cover raise Exception("CumulusCI requires Python 3.8+.") diff --git a/cumulusci/version.txt b/cumulusci/version.txt deleted file mode 100644 index 9993b95e3a..0000000000 --- a/cumulusci/version.txt +++ /dev/null @@ -1 +0,0 @@ -3.71.0 diff --git a/docs/history.md b/docs/history.md index fcd075a06b..4df71f9632 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,7 @@ # History + + ## 3.71.0 (2022-12-20) Critical Changes 🎉 @@ -12,6 +14,8 @@ Changes 🎉 - CumulusCI now offers the ability to inject arbitrary IDs into a project's metadata components. See the [Find-and-Replace Id Injection](https://cumulusci.readthedocs.io/en/latest/deploy.html#find-and-replace-id-injection) source transform for more information. (#3460) - CumulusCI now allows for injecting the current running user's username into a project's metadata components. See the [Find-and-Replace Id Injection](https://cumulusci.readthedocs.io/en/latest/deploy.html#find-and-replace-current-username-injection) source transform for more information. (#3460) + + ## 3.70.0 (2022-11-29) - The `retrieve_changes` and `list_changes` tasks now properly exclude metadata types that `SFDX` is unable to process. These include: `AuraDefinition`, `ExperienceResource`, and `LightningComponentResource` by @jstvz in https://github.com/SFDO-Tooling/CumulusCI/pull/3443 diff --git a/pyproject.toml b/pyproject.toml index f99a886368..41f80ec4c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,140 @@ +[build-system] +requires = ["hatchling", "hatch-fancy-pypi-readme"] +build-backend = "hatchling.build" + +[project] +name = "cumulusci-test" +dynamic = ["readme", "version"] +description = "Build and release tools for Salesforce developers" +license = {text = "BSD 3-Clause License"} +requires-python = ">=3.8" +authors = [ + { name = "Salesforce.org", email = "sfdo-mrbelvedere@salesforce.com" }, +] +keywords = [ + "cumulusci", + "salesforce" +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", +] +dependencies = [ + "click", + "cryptography", + "python-dateutil", + "docutils<0.17", + "Faker", + "fs", + "github3.py", + "jinja2", + "keyring<=23.0.1", + "defusedxml", + "lxml", + "MarkupSafe", + "psutil", + "pydantic", + "PyJWT", + "pytz", + "pyyaml", + "requests", + "requests-futures", + "rich", + "robotframework", + "robotframework-lint", + "robotframework-pabot", + "robotframework-requests", + "robotframework-seleniumlibrary<6", + "rst2ansi", + "salesforce-bulk", + "sarge", + "selenium<4", + "simple-salesforce==1.11.4", + "snowfakery", + "SQLAlchemy", + "xmltodict", +] + +[project.optional-dependencies] +docs = [ + "myst-parser", + "Sphinx" +] +lint = [ + "black", + "flake8<4", + "isort", + "pre-commit" +] +test = [ + "coverage[toml]", + "coveralls", + "factory-boy", + "furo", + "jsonschema", + "pytest<7.1 ", # https://github.com/pytest-dev/pytest/issues/9765 + "pytest-cov", + "pytest-random-order", + "pytest-vcr", + "responses", + "testfixtures", + "tox", + "typeguard", + "vcrpy" +] + +[project.scripts] +cci = "cumulusci.cli.cci:main" +snowfakery = "snowfakery.cli:main" + +[project.urls] +Homepage = "https://github.com/SFDO-Tooling/CumulusCI" +Changelog = "https://cumulusci.readthedocs.io/en/stable/history.html" +"Bug Tracker" = "https://github.com/SFDO-Tooling/CumulusCI/issues" + +[tool.hatch.version] +path = "cumulusci/__about__.py" + +[tool.hatch.build] +include = [ + "/cumulusci", + '/cumulusci/**/*.*', # y[a]ml, js[on], etc. +] + +[tool.hatch.build.targets.sdist] +include = [ + "/cumulusci", + "/requirements/*", # Needed by tox +] + +[tool.hatch.build.targets.wheel] +exclude = [ + "tests/", + "*.sql", + "*.zip" +] + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "README.md" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "docs/history.md" +start-after = "\n\n" +end-before = "\n\n" + +####################### +# Tool configurations # +####################### + [tool.black] exclude = '^/(\.|dist|pybuild|venv)' diff --git a/requirements/dev.txt b/requirements/dev.txt index 34da71e4ab..43643ad145 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,247 +1,301 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: # -# pip-compile requirements/dev.in +# pip-compile --all-extras --output-file=requirements/dev.txt pyproject.toml # alabaster==0.7.12 # via sphinx +appdirs==1.4.4 + # via fs attrs==22.1.0 # via # jsonschema # pytest +authlib==1.1.0 + # via simple-salesforce babel==2.11.0 # via sphinx beautifulsoup4==4.11.1 # via furo black==22.10.0 - # via -r requirements/dev.in -bleach==5.0.1 - # via readme-renderer + # via cumulusci (pyproject.toml) +cachetools==5.2.0 + # via tox certifi==2022.9.24 # via - # -c requirements/prod.txt # requests + # snowfakery cffi==1.15.1 - # via - # -c requirements/prod.txt - # cryptography + # via cryptography cfgv==3.3.1 # via pre-commit +chardet==5.1.0 + # via tox charset-normalizer==2.1.1 # via - # -c requirements/prod.txt # requests + # snowfakery click==8.1.3 # via - # -c requirements/prod.txt # black + # cumulusci (pyproject.toml) + # snowfakery +colorama==0.4.6 + # via tox commonmark==0.9.1 - # via - # -c requirements/prod.txt - # rich + # via rich coverage[toml]==6.5.0 # via - # -r requirements/dev.in # coveralls + # cumulusci (pyproject.toml) # pytest-cov coveralls==3.3.1 - # via -r requirements/dev.in + # via cumulusci (pyproject.toml) cryptography==38.0.3 # via - # -c requirements/prod.txt - # secretstorage + # authlib + # cumulusci (pyproject.toml) + # pyjwt +defusedxml==0.7.1 + # via cumulusci (pyproject.toml) distlib==0.3.6 # via virtualenv docopt==0.6.2 # via coveralls docutils==0.16 # via - # -c requirements/prod.txt + # cumulusci (pyproject.toml) # myst-parser - # readme-renderer # sphinx factory-boy==3.2.1 - # via -r requirements/dev.in + # via cumulusci (pyproject.toml) faker==15.0.0 # via - # -c requirements/prod.txt + # cumulusci (pyproject.toml) # factory-boy -filelock==3.8.0 + # snowfakery +filelock==3.8.2 # via # tox # virtualenv flake8==3.9.2 - # via -r requirements/dev.in -furo==2022.9.29 - # via -r requirements/dev.in -identify==2.5.8 + # via cumulusci (pyproject.toml) +fs==2.4.16 + # via cumulusci (pyproject.toml) +furo==2022.12.7 + # via cumulusci (pyproject.toml) +github3-py==3.2.0 + # via cumulusci (pyproject.toml) +greenlet==1.1.3.post0 + # via + # snowfakery + # sqlalchemy +gvgen==1.0 + # via snowfakery +identify==2.5.9 # via pre-commit idna==3.4 # via - # -c requirements/prod.txt # requests + # snowfakery # yarl imagesize==1.4.1 # via sphinx importlib-metadata==5.0.0 # via - # -c requirements/prod.txt # keyring # sphinx - # twine -importlib-resources==5.10.0 +importlib-resources==5.10.1 # via jsonschema iniconfig==1.1.1 # via pytest isort==5.10.1 - # via -r requirements/dev.in -jeepney==0.8.0 - # via - # -c requirements/prod.txt - # keyring - # secretstorage + # via cumulusci (pyproject.toml) jinja2==3.1.2 # via - # -c requirements/prod.txt + # cumulusci (pyproject.toml) # myst-parser + # snowfakery # sphinx -jsonschema==4.17.0 - # via -r requirements/dev.in +jsonschema==4.17.3 + # via cumulusci (pyproject.toml) keyring==23.0.1 - # via - # -c requirements/prod.txt - # twine + # via cumulusci (pyproject.toml) +lxml==4.9.1 + # via cumulusci (pyproject.toml) markdown-it-py==2.1.0 # via # mdit-py-plugins # myst-parser markupsafe==2.1.1 # via - # -c requirements/prod.txt + # cumulusci (pyproject.toml) # jinja2 + # snowfakery mccabe==0.6.1 # via flake8 -mdit-py-plugins==0.3.1 +mdit-py-plugins==0.3.3 # via myst-parser mdurl==0.1.2 # via markdown-it-py -multidict==6.0.2 +multidict==6.0.3 # via yarl mypy-extensions==0.4.3 # via black myst-parser==0.18.1 - # via -r requirements/dev.in + # via cumulusci (pyproject.toml) +natsort==8.2.0 + # via robotframework-pabot nodeenv==1.7.0 # via pre-commit -packaging==21.3 +packaging==22.0 # via + # pyproject-api # pytest # sphinx # tox -pathspec==0.10.1 +pathspec==0.10.2 # via black -pkginfo==1.8.3 - # via twine pkgutil-resolve-name==1.3.10 # via jsonschema -platformdirs==2.5.3 +platformdirs==2.6.0 # via # black + # tox # virtualenv pluggy==1.0.0 # via # pytest # tox pre-commit==2.20.0 - # via -r requirements/dev.in + # via cumulusci (pyproject.toml) +psutil==5.9.4 + # via cumulusci (pyproject.toml) py==1.11.0 - # via - # pytest - # tox + # via pytest pycodestyle==2.7.0 # via flake8 pycparser==2.21 + # via cffi +pydantic==1.10.2 # via - # -c requirements/prod.txt - # cffi + # cumulusci (pyproject.toml) + # snowfakery pyflakes==2.3.1 # via flake8 pygments==2.13.0 # via - # -c requirements/prod.txt # furo - # readme-renderer # rich # sphinx -pyparsing==3.0.9 - # via packaging +pyjwt[crypto]==2.6.0 + # via + # cumulusci (pyproject.toml) + # github3-py +pyproject-api==1.2.1 + # via tox pyrsistent==0.19.2 # via jsonschema pytest==7.0.1 # via - # -r requirements/dev.in + # cumulusci (pyproject.toml) # pytest-cov # pytest-random-order # pytest-vcr pytest-cov==4.0.0 - # via -r requirements/dev.in -pytest-random-order==1.0.4 - # via -r requirements/dev.in + # via cumulusci (pyproject.toml) +pytest-random-order==1.1.0 + # via cumulusci (pyproject.toml) pytest-vcr==1.0.2 - # via -r requirements/dev.in + # via cumulusci (pyproject.toml) +python-baseconv==1.2.2 + # via snowfakery python-dateutil==2.8.2 # via - # -c requirements/prod.txt + # cumulusci (pyproject.toml) # faker + # github3-py + # snowfakery pytz==2022.6 # via - # -c requirements/prod.txt # babel + # cumulusci (pyproject.toml) pyyaml==6.0 # via - # -c requirements/prod.txt + # cumulusci (pyproject.toml) # myst-parser # pre-commit + # snowfakery # vcrpy -readme-renderer==37.3 - # via twine requests==2.28.1 # via - # -c requirements/prod.txt # coveralls - # requests-toolbelt + # cumulusci (pyproject.toml) + # github3-py + # requests-futures # responses + # robotframework-requests + # salesforce-bulk + # simple-salesforce + # snowfakery # sphinx - # twine -requests-toolbelt==0.10.1 - # via twine +requests-futures==1.0.0 + # via cumulusci (pyproject.toml) responses==0.22.0 - # via -r requirements/dev.in -rfc3986==2.0.0 - # via twine + # via cumulusci (pyproject.toml) rich==12.6.0 - # via - # -c requirements/prod.txt - # twine -secretstorage==3.3.3 - # via - # -c requirements/prod.txt - # keyring + # via cumulusci (pyproject.toml) +robotframework==6.0.1 + # via + # cumulusci (pyproject.toml) + # robotframework-lint + # robotframework-pabot + # robotframework-requests + # robotframework-seleniumlibrary + # robotframework-stacktrace +robotframework-lint==1.1 + # via cumulusci (pyproject.toml) +robotframework-pabot==2.8.0 + # via cumulusci (pyproject.toml) +robotframework-pythonlibcore==4.0.0 + # via robotframework-seleniumlibrary +robotframework-requests==0.9.4 + # via cumulusci (pyproject.toml) +robotframework-seleniumlibrary==5.1.3 + # via cumulusci (pyproject.toml) +robotframework-stacktrace==0.4.1 + # via robotframework-pabot +rst2ansi==0.1.5 + # via cumulusci (pyproject.toml) +salesforce-bulk==2.2.0 + # via cumulusci (pyproject.toml) +sarge==0.1.7.post1 + # via cumulusci (pyproject.toml) +selenium==3.141.0 + # via + # cumulusci (pyproject.toml) + # robotframework-seleniumlibrary +simple-salesforce==1.11.4 + # via + # cumulusci (pyproject.toml) + # salesforce-bulk six==1.16.0 # via - # -c requirements/prod.txt - # bleach + # fs # python-dateutil - # tox + # salesforce-bulk + # snowfakery # vcrpy snowballstemmer==2.2.0 # via sphinx +snowfakery==3.4.0 + # via cumulusci (pyproject.toml) soupsieve==2.3.2.post1 # via beautifulsoup4 sphinx==5.3.0 # via - # -r requirements/dev.in + # cumulusci (pyproject.toml) # furo # myst-parser # sphinx-basic-ng @@ -259,8 +313,12 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -testfixtures==7.0.3 - # via -r requirements/dev.in +sqlalchemy==1.4.41 + # via + # cumulusci (pyproject.toml) + # snowfakery +testfixtures==7.0.4 + # via cumulusci (pyproject.toml) toml==0.10.2 # via # pre-commit @@ -269,49 +327,50 @@ tomli==2.0.1 # via # black # coverage + # pyproject-api # pytest # tox -tox==3.27.0 - # via -r requirements/dev.in -twine==4.0.1 - # via -r requirements/dev.in +tox==4.0.3 + # via cumulusci (pyproject.toml) typeguard==2.13.3 - # via -r requirements/dev.in -types-toml==0.10.8 + # via cumulusci (pyproject.toml) +types-toml==0.10.8.1 # via responses typing-extensions==4.4.0 # via - # -c requirements/prod.txt # black # myst-parser + # pydantic # rich + # snowfakery +unicodecsv==0.14.1 + # via salesforce-bulk +uritemplate==4.1.1 + # via github3-py urllib3==1.26.12 # via - # -c requirements/prod.txt # requests # responses - # twine + # selenium + # snowfakery vcrpy==4.2.1 # via - # -r requirements/dev.in + # cumulusci (pyproject.toml) # pytest-vcr -virtualenv==20.16.6 +virtualenv==20.17.1 # via # pre-commit # tox -webencodings==0.5.1 - # via bleach -wheel==0.38.4 - # via -r requirements/dev.in wrapt==1.14.1 # via vcrpy -yarl==1.8.1 +xmltodict==0.13.0 + # via cumulusci (pyproject.toml) +yarl==1.8.2 # via vcrpy zipp==3.10.0 # via - # -c requirements/prod.txt - # importlib-metadata - # importlib-resources + # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 180fb9ea6f..0000000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[build] -build-base = pybuild - -[bdist_wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index df46f10cf9..0000000000 --- a/setup.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os.path -import re -from pkgutil import walk_packages - -from setuptools import setup - - -def find_packages(path=["."], prefix=""): - yield prefix - prefix = prefix + "." - for _, name, ispkg in walk_packages(path, prefix): - if ispkg: - yield name - - -with open(os.path.join("cumulusci", "version.txt"), "r") as version_file: - version = version_file.read().strip() - -with open("README.md", "rb") as readme_file: - readme = readme_file.read().decode("utf-8") - -with open("docs/history.md", "rb") as history_file: - history = history_file.read().decode("utf-8") - -with open("requirements/prod.txt") as requirements_file: - requirements = [] - for req in requirements_file.read().splitlines(): - # skip comments and hash lines - if re.match(r"\s*#", req) or re.match(r"\s*--hash", req): - continue - else: - req = req.split(" ")[0] - # Work around normalized name of github3.py distribution - req = req.replace("github3-py", "github3.py") - requirements.append(req) - -setup( - name="cumulusci", - version=version, - description="Build and release tools for Salesforce developers", - long_description=readme + "\n\n" + history, - long_description_content_type="text/markdown", - author="Salesforce.org", - author_email="sfdo-mrbelvedere@salesforce.com", - url="https://github.com/SFDO-Tooling/CumulusCI", - packages=list(find_packages(["cumulusci"], "cumulusci")), - package_dir={"cumulusci": "cumulusci"}, - entry_points={ - "console_scripts": [ - "cci=cumulusci.cli.cci:main", - "snowfakery=snowfakery.cli:main", - ] - }, - include_package_data=True, - install_requires=requirements, - license="BSD license", - zip_safe=False, - keywords="cumulusci", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - ], - test_suite="cumulusci.core.tests", - python_requires=">=3.8", -) diff --git a/utility/update-history.py b/utility/update-history.py new file mode 100644 index 0000000000..028a279ff3 --- /dev/null +++ b/utility/update-history.py @@ -0,0 +1,15 @@ +""" +Update the history file. +""" +from pathlib import Path + +START_MARKER = "" +STOP_MARKER = "" + +history = Path("docs/history.md").read_text() +latest = Path("changelog.md").read_text() +updated = history.replace(f"{STOP_MARKER}\n\n", "").replace( + f"{START_MARKER}\n\n", f"{START_MARKER}\n\n{latest}\n\n{STOP_MARKER}\n\n" +) + +Path("docs/history.md").write_text(updated) From 99c7adcff1dda11233c2592276ee2f36afe32205 Mon Sep 17 00:00:00 2001 From: Paul Prescod Date: Tue, 17 Jan 2023 15:54:17 -0800 Subject: [PATCH 2/5] Improve documentation of tasks w.r.t required/option/defauled fields. (#3447) Right now it is typical to have an option description that looks like this: ``` --path PATH Required The path to the metadata source to be deployed Default: unpackaged/config/qa ``` This is paradoxical. Either the option is required, or it is defaulted. It can't be both. The new format looks like this: ``` --path PATH The path to the metadata source to be deployed Default: unpackaged/config/qa --unmanaged UNMANAGED If True, changes namespace_inject to replace tokens with a blank string Optional ``` "Optional" or "Required" is only printed if there is no default. --- cumulusci/tests/test_utils.py | 16 +++++++--------- cumulusci/utils/__init__.py | 9 ++++----- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/cumulusci/tests/test_utils.py b/cumulusci/tests/test_utils.py index 8469862d4b..6d899fda55 100644 --- a/cumulusci/tests/test_utils.py +++ b/cumulusci/tests/test_utils.py @@ -236,16 +236,16 @@ def test_doc_task(self, task_config): ``$ cci task run scoop_icecream``\n\n Options\n------------------------------------------\n\n ``--flavor VANILLA`` -\t *Required*\n -\t What flavor\n + +\t What flavor +\n *Required*\n \t Type: string\n -``--color COLOR`` -\t *Optional*\n +``--color COLOR``\n \t What color\n \t Default: black\n ``--size SIZE`` -\t *Optional*\n -\t How big""" +\n\t How big +\n *Optional*""" ) def test_get_command_syntax(self, task_config): @@ -285,15 +285,13 @@ def test_get_option_usage_string(self, option_info): def test_create_task_options_doc(self, option_info): option_one_doc = utils.create_task_options_doc(option_info[:1]) option_two_doc = utils.create_task_options_doc(option_info[1:]) - assert option_one_doc == [ - "\t *Required*", "\n\t description", "\n\t Default: default", "\n\t Type: option_type", ] - assert option_two_doc == ["\t *Optional*", "\n\t Brief description here."] + assert option_two_doc == ["\n\t Brief description here.", "\n *Optional*"] def test_document_flow(self): project_config = create_project_config("TestOwner", "TestRepo") diff --git a/cumulusci/utils/__init__.py b/cumulusci/utils/__init__.py index 5f33d5c21b..605818d96c 100644 --- a/cumulusci/utils/__init__.py +++ b/cumulusci/utils/__init__.py @@ -440,11 +440,6 @@ def create_task_options_doc(task_options): if usage_str: doc.append(f"\n``{usage_str}``") - if option.get("required"): - doc.append("\t *Required*") - else: - doc.append("\t *Optional*") - description = option.get("description") if description: doc.append(f"\n\t {description}") @@ -452,6 +447,10 @@ def create_task_options_doc(task_options): default = option.get("default") if default: doc.append(f"\n\t Default: {default}") + elif option.get("required"): + doc.append("\n *Required*") + else: + doc.append("\n *Optional*") option_type = option.get("option_type") if option_type: From e0fbbc57a6420265314f3429c1054c52e4f4f21a Mon Sep 17 00:00:00 2001 From: Paul Prescod Date: Tue, 17 Jan 2023 16:16:18 -0800 Subject: [PATCH 3/5] Add a better error message for scratch orgs that cannot be recreated (#3469) 1. Fix up some imports to "modern" syntax. Makes Pylance happy 2. Add a better error for the case where a scratch org config went away after a scratch org was created and before the system tries to re-create it based on its name. 3. Add some assertions that Pylance likes about missing orgs 4. Add a test for the new code. Co-authored-by: James Estevez --- cumulusci/core/keychain/base_project_keychain.py | 15 +++++++++------ .../keychain/tests/test_base_project_keychain.py | 6 ++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cumulusci/core/keychain/base_project_keychain.py b/cumulusci/core/keychain/base_project_keychain.py index 10795875e8..78f40a9035 100644 --- a/cumulusci/core/keychain/base_project_keychain.py +++ b/cumulusci/core/keychain/base_project_keychain.py @@ -1,11 +1,8 @@ import sarge -from cumulusci.core.config import ( - BaseConfig, - ConnectedAppOAuthConfig, - ScratchOrgConfig, - ServiceConfig, -) +from cumulusci.core.config import ConnectedAppOAuthConfig, ServiceConfig +from cumulusci.core.config.base_config import BaseConfig +from cumulusci.core.config.scratch_org_config import ScratchOrgConfig from cumulusci.core.exceptions import ( CumulusCIException, CumulusCIUsageError, @@ -60,6 +57,8 @@ def _validate_key(self): def create_scratch_org(self, org_name, config_name, days=None, set_password=True): """Adds/Updates a scratch org config to the keychain from a named config""" scratch_config = self.project_config.lookup(f"orgs__scratch__{config_name}") + if scratch_config is None: + raise OrgNotFound(f"No such org configured: `{config_name}`") if days is not None: # Allow override of scratch config's default days scratch_config["days"] = days @@ -86,6 +85,7 @@ def set_org(self, org_config, global_org=False, save=True): def set_default_org(self, name): """set the default org for tasks and flows by name""" org = self.get_org(name) + assert org is not None self.unset_default_org() org.config["default"] = True org.save() @@ -100,6 +100,7 @@ def unset_default_org(self): """unset the default orgs for tasks""" for org in self.list_orgs(): org_config = self.get_org(org) + assert org_config is not None if org_config.default: del org_config.config["default"] org_config.save() @@ -114,6 +115,7 @@ def get_default_org(self): """retrieve the name and configuration of the default org""" for org in self.list_orgs(): org_config = self.get_org(org) + assert org_config is not None if org_config.default: return org, org_config return None, None @@ -121,6 +123,7 @@ def get_default_org(self): def get_org(self, name: str): """retrieve an org configuration by name key""" org = self._get_org(name) + assert org if org.keychain: assert org.keychain is self else: diff --git a/cumulusci/core/keychain/tests/test_base_project_keychain.py b/cumulusci/core/keychain/tests/test_base_project_keychain.py index 6f18f3f173..06e9757fdc 100644 --- a/cumulusci/core/keychain/tests/test_base_project_keychain.py +++ b/cumulusci/core/keychain/tests/test_base_project_keychain.py @@ -290,3 +290,9 @@ def test_remove_org( keychain.remove_org("test") assert "test" not in keychain.orgs keychain.cleanup_org_cache_dirs.assert_called_once() + + def test_org_definition__missing(self, project_config, key): + """What if a scratch org was created with a YAML definition which was deleted more recently?""" + keychain = BaseProjectKeychain(project_config, key) + with pytest.raises(OrgNotFound, match="No such org"): + keychain.create_scratch_org("no_such_org", "no_such_org") From f0a97030c4ca571c74eda9ea60fe07ab11733023 Mon Sep 17 00:00:00 2001 From: Paul Prescod Date: Thu, 19 Jan 2023 16:51:39 -0800 Subject: [PATCH 4/5] Close all test zip files (#3494) Co-authored-by: James Estevez --- cumulusci/salesforce_api/tests/test_package_zip.py | 1 + cumulusci/tasks/salesforce/tests/test_CreatePackage.py | 1 + cumulusci/tasks/salesforce/tests/test_Deploy.py | 4 ++++ cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py | 1 + cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py | 1 + .../salesforce/tests/test_UninstallPackagedIncremental.py | 1 + cumulusci/tasks/salesforce/tests/test_base_tasks.py | 1 + cumulusci/tasks/salesforce/tests/test_org_settings.py | 4 ++++ cumulusci/tasks/tests/test_create_package_version.py | 1 + cumulusci/tests/test_utils.py | 4 ++++ 10 files changed, 19 insertions(+) diff --git a/cumulusci/salesforce_api/tests/test_package_zip.py b/cumulusci/salesforce_api/tests/test_package_zip.py index 56e78ed958..fe4a85bbd3 100644 --- a/cumulusci/salesforce_api/tests/test_package_zip.py +++ b/cumulusci/salesforce_api/tests/test_package_zip.py @@ -153,6 +153,7 @@ def test_builder(self, task_context): "objects/CustomObject__c", "objects/does-not-exist-in-schema/some.file", } + zf.close() def test_add_files_to_package(self, task_context): with temporary_dir() as path: diff --git a/cumulusci/tasks/salesforce/tests/test_CreatePackage.py b/cumulusci/tasks/salesforce/tests/test_CreatePackage.py index 088672ccee..7cc8b49eee 100644 --- a/cumulusci/tasks/salesforce/tests/test_CreatePackage.py +++ b/cumulusci/tasks/salesforce/tests/test_CreatePackage.py @@ -19,3 +19,4 @@ def test_get_package_zip(self): zf = zipfile.ZipFile(io.BytesIO(base64.b64decode(package_zip)), "r") package_xml = zf.read("package.xml") assert b"TestPackage" in package_xml + zf.close() diff --git a/cumulusci/tasks/salesforce/tests/test_Deploy.py b/cumulusci/tasks/salesforce/tests/test_Deploy.py index eee661b797..a08211f117 100644 --- a/cumulusci/tasks/salesforce/tests/test_Deploy.py +++ b/cumulusci/tasks/salesforce/tests/test_Deploy.py @@ -32,6 +32,7 @@ def test_get_api(self): api = task._get_api() zf = zipfile.ZipFile(io.BytesIO(base64.b64decode(api.package_zip)), "r") assert "package.xml" in zf.namelist() + zf.close() def test_get_api__managed(self): with temporary_dir() as path: @@ -43,6 +44,7 @@ def test_get_api__managed(self): api = task._get_api() zf = zipfile.ZipFile(io.BytesIO(base64.b64decode(api.package_zip)), "r") assert "package.xml" in zf.namelist() + zf.close() def test_get_api__additional_options(self): with temporary_dir() as path: @@ -76,6 +78,7 @@ def test_get_api__skip_clean_meta_xml(self): api = task._get_api() zf = zipfile.ZipFile(io.BytesIO(base64.b64decode(api.package_zip)), "r") assert "package.xml" in zf.namelist() + zf.close() def test_get_api__static_resources(self): with temporary_dir() as path: @@ -115,6 +118,7 @@ def test_get_api__static_resources(self): package_xml = zf.read("package.xml").decode() assert "StaticResource" in package_xml assert "TestBundle" in package_xml + zf.close() def test_get_api__missing_path(self): task = create_task( diff --git a/cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py b/cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py index 352b40cc19..c61a7b82c8 100644 --- a/cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py +++ b/cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py @@ -21,3 +21,4 @@ def test_run_task(self): task.api_class = mock.Mock(return_value=mock.Mock(return_value=zf)) task() assert os.path.exists(os.path.join(path, "testfile")) + zf.close() diff --git a/cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py b/cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py index a1eec90566..ec9ed357d1 100644 --- a/cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py +++ b/cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py @@ -37,6 +37,7 @@ def test_get_destructive_changes(self, ApiRetrievePackaged): """ == result ) + zf.close() @responses.activate def test_error_handling(self): diff --git a/cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py b/cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py index 7b09a73a07..24f49beacd 100644 --- a/cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py +++ b/cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py @@ -94,6 +94,7 @@ def test_get_destructive_changes(self): """ == result ) + zf.close() def test_get_destructive_changes__no_package_xml(self): project_config = create_project_config() diff --git a/cumulusci/tasks/salesforce/tests/test_base_tasks.py b/cumulusci/tasks/salesforce/tests/test_base_tasks.py index d30461b3f5..fdab8cfe4b 100644 --- a/cumulusci/tasks/salesforce/tests/test_base_tasks.py +++ b/cumulusci/tasks/salesforce/tests/test_base_tasks.py @@ -87,6 +87,7 @@ def test_process_namespace(self): zf = zipfile.ZipFile(io.BytesIO(), "w") result = task._process_namespace(zf) assert isinstance(result, zipfile.ZipFile) + zf.close() class TestBaseUninstallMetadata: diff --git a/cumulusci/tasks/salesforce/tests/test_org_settings.py b/cumulusci/tasks/salesforce/tests/test_org_settings.py index 7fa0d724ea..8ce6b34f89 100644 --- a/cumulusci/tasks/salesforce/tests/test_org_settings.py +++ b/cumulusci/tasks/salesforce/tests/test_org_settings.py @@ -93,6 +93,7 @@ def test_run_task__json_only(self): """ ) + zf.close() def test_run_task__json_only__with_org_settings(self): with temporary_dir() as d: @@ -137,6 +138,7 @@ def test_run_task__json_only__with_org_settings(self): 48.0 """ ) + zf.close() def test_run_task__settings_only(self): settings = { @@ -187,6 +189,7 @@ def test_run_task__settings_only(self): """ ) + zf.close() def test_run_task__json_and_settings(self): with temporary_dir() as d: @@ -251,6 +254,7 @@ def test_run_task__json_and_settings(self): """ ) + zf.close() def test_run_task__no_settings(self): with temporary_dir() as d: diff --git a/cumulusci/tasks/tests/test_create_package_version.py b/cumulusci/tasks/tests/test_create_package_version.py index 7bd4bdf9e9..a870c4c19d 100644 --- a/cumulusci/tasks/tests/test_create_package_version.py +++ b/cumulusci/tasks/tests/test_create_package_version.py @@ -459,6 +459,7 @@ def test_run_task( assert task.return_values["dependencies"] == [ {"version_id": "04t000000000009AAA"} ] + zf.close() @responses.activate def test_get_or_create_package__namespaced_existing( diff --git a/cumulusci/tests/test_utils.py b/cumulusci/tests/test_utils.py index 6d899fda55..74d872e7d4 100644 --- a/cumulusci/tests/test_utils.py +++ b/cumulusci/tests/test_utils.py @@ -448,6 +448,7 @@ def process(name, content): result = zf.read("test") # assert contents were untouched assert contents == result + zf.close() def test_inject_namespace__managed(self): logger = mock.Mock() @@ -521,6 +522,7 @@ def test_zip_clean_metaxml(self): result = zf.read("classes/test-meta.xml") assert b"packageVersions" not in result assert "other/test-meta.xml" in zf.namelist() + zf.close() def test_zip_clean_metaxml__skips_binary(self): logger = mock.Mock() @@ -531,6 +533,7 @@ def test_zip_clean_metaxml__skips_binary(self): zf = utils.zip_clean_metaxml(zf, logger=logger) assert "classes/test-meta.xml" in zf.namelist() + zf.close() def test_zip_clean_metaxml__handles_nonascii(self): zf = zipfile.ZipFile(io.BytesIO(), "w") @@ -538,6 +541,7 @@ def test_zip_clean_metaxml__handles_nonascii(self): zf = utils.zip_clean_metaxml(zf) assert b"\xc3\xb1" == zf.read("classes/test-meta.xml") + zf.close() def test_doc_task_not_inherited(self): task_config = TaskConfig( From 2bb633a72034dcd5842446eb69b3ec25c265e15d Mon Sep 17 00:00:00 2001 From: James Estevez Date: Fri, 20 Jan 2023 16:46:51 -0800 Subject: [PATCH 5/5] Remove deprecated Playwright keywords (#3503) We missed a major release of `robotframework-browser` ([v15]), which removed the deprecated `execute_javascript` keyword. This resulted in test failures that we initially believed were timeout related because the `AttributeError` was being cought by the bare `except` and re-raised with an misleading error message. This commit resolves the falures by: 1. Switching to the `evaluate_javascript` keyword 2. Raising the timeout exception using the `raise ... from exc` [v15]: https://github.com/MarketSquare/robotframework-browser/releases/tag/v15.0.0 --- cumulusci/robotframework/SalesforcePlaywright.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cumulusci/robotframework/SalesforcePlaywright.py b/cumulusci/robotframework/SalesforcePlaywright.py index 5232111587..9eba2edf11 100644 --- a/cumulusci/robotframework/SalesforcePlaywright.py +++ b/cumulusci/robotframework/SalesforcePlaywright.py @@ -31,12 +31,12 @@ def get_current_record_id(self): This expects the url to contain an id that matches [a-zA-Z0-9]{15,18} """ OID_REGEX = r"^(%2F)?([a-zA-Z0-9]{15,18})$" - url = self.browser.execute_javascript("window.location.href") + url = self.browser.evaluate_javascript(None, "window.location.href") for part in url.split("/"): oid_match = re.match(OID_REGEX, part) if oid_match is not None: - return oid_match.group(2) - raise AssertionError("Could not parse record id from url: {}".format(url)) + return oid_match[2] + raise AssertionError(f"Could not parse record id from url: {url}") def go_to_record_home(self, obj_id): """Navigates to the Home view of a Salesforce Object @@ -45,7 +45,7 @@ def go_to_record_home(self, obj_id): div can be found on the page. """ url = self.cumulusci.org.lightning_base_url - url = "{}/lightning/r/{}/view".format(url, obj_id) + url = f"{url}/lightning/r/{obj_id}/view" self.browser.go_to(url) self.wait_until_loading_is_complete("div.slds-page-header_record-home") @@ -134,7 +134,8 @@ class 'slds-template__container', but a different locator can else locator ) self.browser.get_elements(locator) - self.browser.execute_javascript(function=WAIT_FOR_AURA_SCRIPT) + + self.browser.evaluate_javascript(None, WAIT_FOR_AURA_SCRIPT) # An old knowledge article recommends waiting a second. I don't # like it, but it seems to help. We should do a wait instead, # but I can't figure out what to wait on. @@ -173,14 +174,14 @@ def wait_until_salesforce_is_ready( # No errors? We're golden. break - except Exception: + except Exception as exc: # dang. Maybe we landed somewhere unexpected? if self._check_for_classic(): continue if time.time() - start_time > timeout_seconds: self.browser.take_screenshot() - raise Exception("Timed out waiting for a lightning page") + raise Exception("Timed out waiting for a lightning page") from exc # If at first you don't succeed, ... self.browser.go_to(login_url)