diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..bde39a55 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,68 @@ +name: 🐛 Bug report +description: Create a bug report +body: + - type: checkboxes + attributes: + label: Are you certain it's a bug? + description: If you're uncertain, please report at https://github.com/serverless/serverless-python-requirements/discussions instead + options: + - label: Yes, it looks like a bug + required: true + - type: checkboxes + attributes: + label: Are you using the latest plugin release? + description: Latest version can be checked at https://github.com/serverless/serverless-python-requirements/releases/latest + options: + - label: Yes, I'm using the latest plugin release + required: true + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists + options: + - label: I have searched existing issues, it hasn't been reported yet + required: true + - type: textarea + attributes: + label: Issue description + validations: + required: true + - type: textarea + attributes: + label: Service configuration (serverless.yml) content + description: | + Provide COMPLETE content of serverless.yml, ensuring that: + • It consistently reproduces described issue + • It's as minimal as possible + • Ideally with no other plugins involved + • Has sensitive parts masked out + + If not applicable, fill with "N/A" + render: yaml + validations: + required: true + - type: input + attributes: + label: Command name and used flags + description: | + Full command name with used flags (If not applicable, fill with "N/A") + placeholder: serverless [...flags] + validations: + required: true + - type: textarea + attributes: + label: Command output + description: | + COMPLETE command output. + + If not applicable, fill with "N/A" + render: shell + validations: + required: true + - type: textarea + attributes: + label: Environment information + description: '"serverless --version" output + used version of the plugin' + render: shell + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..a7f83c6b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Question + url: https://github.com/serverless/serverless-python-requirements/discussions + about: Please ask and answer questions here diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000..14907ec2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,21 @@ +name: 🎉 Feature request +description: Suggest an idea +body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists + options: + - label: I have searched existing issues, it hasn't been reported yet + required: true + - type: textarea + attributes: + label: Use case description + description: Describe the use case that needs to be addressed + validations: + required: true + - type: textarea + attributes: + label: Proposed solution (optional) + description: | + e.g. propose how the configuration and implementation of the new feature could look diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml index 0d77acfc..1f979b0a 100644 --- a/.github/workflows/integrate.yml +++ b/.github/workflows/integrate.yml @@ -15,7 +15,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: [2.7, 3.6] + sls-version: [2, 3] steps: - name: Checkout repository uses: actions/checkout@v2 @@ -30,10 +30,10 @@ jobs: key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} restore-keys: npm-v14-${{ runner.os }}-${{ github.ref }}- - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.7 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.7 - name: Install Node.js and npm uses: actions/setup-node@v1 @@ -51,7 +51,7 @@ jobs: run: python -m pip install pipenv poetry - name: Install serverless - run: npm install -g serverless@2 + run: npm install -g serverless@${{ matrix.sls-version }} - name: Install dependencies if: steps.cacheNpm.outputs.cache-hit != 'true' @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.6] + sls-version: [2, 3] steps: - name: Checkout repository uses: actions/checkout@v2 @@ -81,10 +81,10 @@ jobs: key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} restore-keys: npm-v14-${{ runner.os }}-${{ github.ref }}- - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.7 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.7 - name: Install Node.js and npm uses: actions/setup-node@v1 @@ -102,7 +102,7 @@ jobs: run: python -m pip install pipenv poetry - name: Install serverless - run: npm install -g serverless@2 + run: npm install -g serverless@${{ matrix.sls-version }} - name: Install dependencies if: steps.cacheNpm.outputs.cache-hit != 'true' @@ -115,9 +115,6 @@ jobs: linuxNode12: name: '[Linux] Node.js v12: Unit tests' runs-on: ubuntu-latest - strategy: - matrix: - python-version: [2.7, 3.6] steps: - name: Checkout repository uses: actions/checkout@v2 @@ -132,10 +129,10 @@ jobs: key: npm-v12-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} restore-keys: npm-v12-${{ runner.os }}-${{ github.ref }}- - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.7 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.7 - name: Install Node.js and npm uses: actions/setup-node@v1 @@ -153,7 +150,7 @@ jobs: run: python -m pip install pipenv poetry - name: Install serverless - run: npm install -g serverless@2 + run: npm install -g serverless@${{ matrix.sls-version }} - name: Install dependencies if: steps.cacheNpm.outputs.cache-hit != 'true' @@ -166,6 +163,9 @@ jobs: tagIfNewVersion: name: Tag if new version runs-on: ubuntu-latest + strategy: + matrix: + sls-version: [2, 3] needs: [windowsNode14, linuxNode14, linuxNode12] steps: - name: Checkout repository diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index b4c245f5..31052279 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.6] + sls-version: [2, 3] steps: - name: Checkout repository uses: actions/checkout@v2 @@ -43,10 +43,10 @@ jobs: npm-v14-${{ runner.os }}-${{ github.ref }}- npm-v14-${{ runner.os }}-refs/heads/master- - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.7 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.7 - name: Install Node.js and npm uses: actions/setup-node@v1 @@ -64,7 +64,7 @@ jobs: run: python -m pip install pipenv poetry - name: Install serverless - run: npm install -g serverless@2 + run: npm install -g serverless@${{ matrix.sls-version }} - name: Install dependencies if: steps.cacheNpm.outputs.cache-hit != 'true' @@ -93,7 +93,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: [2.7, 3.6] + sls-version: [2, 3] steps: - name: Checkout repository uses: actions/checkout@v2 @@ -110,10 +110,10 @@ jobs: npm-v14-${{ runner.os }}-${{ github.ref }}- npm-v14-${{ runner.os }}-refs/heads/master- - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.7 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.7 - name: Install Node.js and npm uses: actions/setup-node@v1 @@ -131,7 +131,7 @@ jobs: run: python -m pip install pipenv poetry - name: Install serverless - run: npm install -g serverless@2 + run: npm install -g serverless@${{ matrix.sls-version }} - name: Install dependencies if: steps.cacheNpm.outputs.cache-hit != 'true' @@ -146,7 +146,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.6] + sls-version: [2, 3] steps: - name: Checkout repository uses: actions/checkout@v2 @@ -163,10 +163,10 @@ jobs: npm-v12-${{ runner.os }}-${{ github.ref }}- npm-v12-${{ runner.os }}-refs/heads/master- - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.7 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.7 - name: Install Node.js and npm uses: actions/setup-node@v1 @@ -184,7 +184,7 @@ jobs: run: python -m pip install pipenv poetry - name: Install serverless - run: npm install -g serverless@2 + run: npm install -g serverless@${{ matrix.sls-version }} - name: Install dependencies if: steps.cacheNpm.outputs.cache-hit != 'true' diff --git a/CHANGELOG.md b/CHANGELOG.md index 41041fd3..fb11a43a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [6.0.0](https://github.com/UnitedIncome/serverless-python-requirements/compare/v5.4.0...v6.0.0) (2022-10-23) + +### ⚠ BREAKING CHANGES + +- Changes default `dockerImage` used for building dependencies (now uses images from `public.ecr.aws/sam` repository) +- Requires `pipenv` in version `2022-04-08` or higher + +### Features + +- Introduce `requirePoetryLockFile` flag ([#728](https://github.com/serverless/serverless-python-requirements/pull/728)) ([e81d9e1](https://github.com/UnitedIncome/serverless-python-requirements/commit/e81d9e1824c135f110b4deccae2c26b0cbb26778)) ([François-Michel L'Heureux](https://github.com/FinchPowers)) +- Switch to official AWS docker images by default ([#724](https://github.com/UnitedIncome/serverless-python-requirements/issues/724)) ([4ba3bbe](https://github.com/UnitedIncome/serverless-python-requirements/commit/4ba3bbeb9296b4844feb476de695f33ee2a30056)) ([Piotr Grzesik](https://github.com/pgrzesik)) + +### Bug Fixes + +- Adapt to support latest `pipenv` version ([#718](https://github.com/UnitedIncome/serverless-python-requirements/issues/718)) ([853da8d](https://github.com/UnitedIncome/serverless-python-requirements/commit/853da8d39921dc83a23d59fd825b2180814f87ff)) ([Anders Steiner](https://github.com/andidev) & [Randy Westergren](https://github.com/rwestergren) & [Piotr Grzesik](https://github.com/pgrzesik)) +- Properly recognize individual function ([#725](https://github.com/UnitedIncome/serverless-python-requirements/issues/725)) ([78795be](https://github.com/UnitedIncome/serverless-python-requirements/commit/78795be24eb08dc78acd7566778b3960c28b263c)) ([Piotr Grzesik](https://github.com/pgrzesik)) + +### Maintenance Improvements + +- Improve error message for docker failures ([#723](https://github.com/serverless/serverless-python-requirements/pull/723))([cc146d0](https://github.com/UnitedIncome/serverless-python-requirements/commit/cc146d088d362187641dd5ae3e9d0129a14c60e2)) ([Piotr Grzesik](https://github.com/pgrzesik)) + ## [5.4.0](https://github.com/UnitedIncome/serverless-python-requirements/compare/v5.3.1...v5.4.0) (2022-03-14) ### Features diff --git a/README.md b/README.md index 63b1a32a..e0f27ac3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![npm](https://img.shields.io/npm/v/serverless-python-requirements.svg)](https://www.npmjs.com/package/serverless-python-requirements) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) -A Serverless v1.x plugin to automatically bundle dependencies from `requirements.txt` and make them available in your `PYTHONPATH`. +A Serverless Framework plugin to automatically bundle dependencies from `requirements.txt` and make them available in your `PYTHONPATH`. --- @@ -109,6 +109,8 @@ custom: ## :sparkles::cake::sparkles: Pipenv support +Requires `pipenv` in version `2022-04-08` or higher. + If you include a `Pipfile` and have `pipenv` installed instead of a `requirements.txt` this will use `pipenv lock -r` to generate them. It is fully compatible with all options such as `zip` and `dockerizePip`. If you don't want this plugin to generate it for you, set the following option: @@ -131,6 +133,15 @@ custom: usePoetry: false ``` +Be aware that if no `poetry.lock` file is present, a new one will be generated on the fly. To help having predictable builds, +you can set the `requirePoetryLockFile` flag to true to throw an error when `poetry.lock` is missing. + +```yaml +custom: + pythonRequirements: + requirePoetryLockFile: false +``` + ### Poetry with git dependencies Poetry by default generates the exported requirements.txt file with `-e` and that breaks pip with `-t` parameter @@ -360,6 +371,9 @@ custom: ### Per-function requirements +**Note: this feature does not work with Pipenv/Poetry, it requires `requirements.txt` +files for your Python modules.** + If you have different python functions, with different sets of requirements, you can avoid including all the unecessary dependencies of your functions by using the following structure: diff --git a/example/serverless.yml b/example/serverless.yml index 9b58ead1..349cdcb8 100644 --- a/example/serverless.yml +++ b/example/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test provider: name: aws - runtime: python3.6 + runtime: python3.7 plugins: - serverless-python-requirements diff --git a/example_native_deps/serverless.yml b/example_native_deps/serverless.yml index 0f4e632a..4deed44a 100644 --- a/example_native_deps/serverless.yml +++ b/example_native_deps/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test provider: name: aws - runtime: python3.6 + runtime: python3.7 plugins: - serverless-python-requirements diff --git a/index.js b/index.js index ebfc4017..5c568cc4 100644 --- a/index.js +++ b/index.js @@ -57,6 +57,7 @@ class ServerlessPythonRequirements { pipCmdExtraArgs: [], noDeploy: [], vendor: '', + requirePoetryLockFile: false, }, (this.serverless.service.custom && this.serverless.service.custom.pythonRequirements) || @@ -94,7 +95,9 @@ class ServerlessPythonRequirements { ); } else if (!options.dockerFile) { // If no dockerFile is provided, use default image - const defaultImage = `lambci/lambda:build-${this.serverless.service.provider.runtime}`; + const architecture = + this.serverless.service.provider.architecture || 'x86_64'; + const defaultImage = `public.ecr.aws/sam/build-${this.serverless.service.provider.runtime}:latest-${architecture}`; options.dockerImage = options.dockerImage || defaultImage; } if (options.layer) { @@ -109,7 +112,7 @@ class ServerlessPythonRequirements { get targetFuncs() { let inputOpt = this.serverless.processedInput.options; return inputOpt.function - ? [inputOpt.functionObj] + ? [this.serverless.service.functions[inputOpt.function]] : values(this.serverless.service.functions).filter((func) => !func.image); } diff --git a/lib/pip.js b/lib/pip.js index ccb809c3..149c0285 100644 --- a/lib/pip.js +++ b/lib/pip.js @@ -412,6 +412,16 @@ async function installRequirements(targetFolder, pluginInstance) { 'PYTHON_REQUIREMENTS_COMMAND_NOT_FOUND' ); } + + if (cmd === 'docker' && e.stderrBuffer) { + throw new pluginInstance.serverless.classes.Error( + `Running "${cmd} ${args.join(' ')}" failed with: "${e.stderrBuffer + .toString() + .trim()}"`, + 'PYTHON_REQUIREMENTS_DOCKER_COMMAND_FAILED' + ); + } + if (log) { log.info(`Stdout: ${e.stdoutBuffer}`); log.info(`Stderr: ${e.stderrBuffer}`); diff --git a/lib/pipenv.js b/lib/pipenv.js index 5856d47b..11331ee3 100644 --- a/lib/pipenv.js +++ b/lib/pipenv.js @@ -28,27 +28,32 @@ async function pipfileToRequirements() { } try { - let res; try { - res = await spawn( - 'pipenv', - ['lock', '--requirements', '--keep-outdated'], - { - cwd: this.servicePath, - } - ); + await spawn('pipenv', ['lock', '--keep-outdated'], { + cwd: this.servicePath, + }); } catch (e) { - if ( - e.stderrBuffer && - e.stderrBuffer.toString().includes('command not found') - ) { + const stderrBufferContent = + (e.stderrBuffer && e.stderrBuffer.toString()) || ''; + + if (stderrBufferContent.includes('must exist to use')) { + // No previous Pipfile.lock, we will try to generate it here + await spawn('pipenv', ['lock'], { + cwd: this.servicePath, + }); + } else if (stderrBufferContent.includes('command not found')) { throw new this.serverless.classes.Error( `pipenv not found! Install it according to the poetry docs.`, 'PYTHON_REQUIREMENTS_PIPENV_NOT_FOUND' ); + } else { + throw e; } - throw e; } + const res = await spawn('pipenv', ['requirements'], { + cwd: this.servicePath, + }); + fse.ensureDirSync(path.join(this.servicePath, '.serverless')); fse.writeFileSync( path.join(this.servicePath, '.serverless/requirements.txt'), diff --git a/lib/poetry.js b/lib/poetry.js index 4003c1df..d324784b 100644 --- a/lib/poetry.js +++ b/lib/poetry.js @@ -21,12 +21,28 @@ async function pyprojectTomlToRequirements(modulePath, pluginInstance) { generateRequirementsProgress = progress.get( 'python-generate-requirements-toml' ); - generateRequirementsProgress.update( - 'Generating requirements.txt from "pyproject.toml"' - ); - log.info('Generating requirements.txt from "pyproject.toml"'); + } + + const emitMsg = (msg) => { + if (generateRequirementsProgress) { + generateRequirementsProgress.update(msg); + log.info(msg); + } else { + serverless.cli.log(msg); + } + }; + + if (fs.existsSync('poetry.lock')) { + emitMsg('Generating requirements.txt from poetry.lock'); } else { - serverless.cli.log('Generating requirements.txt from pyproject.toml...'); + if (options.requirePoetryLockFile) { + throw new serverless.classes.Error( + 'poetry.lock file not found - set requirePoetryLockFile to false to ' + + 'disable this error', + 'MISSING_REQUIRED_POETRY_LOCK' + ); + } + emitMsg('Generating poetry.lock and requirements.txt from pyproject.toml'); } try { diff --git a/package.json b/package.json index 7985cb60..1aeeb4e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-python-requirements", - "version": "5.4.0", + "version": "6.0.0", "engines": { "node": ">=12.0" }, @@ -50,12 +50,12 @@ }, "devDependencies": { "cross-spawn": "*", - "eslint": "^7.32.0", + "eslint": "^8.26.0", "git-list-updated": "^1.2.1", - "github-release-from-cc-changelog": "^2.2.1", + "github-release-from-cc-changelog": "^2.3.0", "lodash": "^4.17.21", "prettier": "^2", - "standard-version": "^9.3.2", + "standard-version": "^9.5.0", "tape": "*", "tape-promise": "*" }, @@ -64,17 +64,17 @@ "appdirectory": "^0.1.0", "bluebird": "^3.7.2", "child-process-ext": "^2.1.1", - "fs-extra": "^9.1.0", + "fs-extra": "^10.1.0", "glob-all": "^3.3.0", "is-wsl": "^2.2.0", - "jszip": "^3.7.1", + "jszip": "^3.10.1", "lodash.get": "^4.4.2", "lodash.uniqby": "^4.7.0", "lodash.values": "^4.3.0", "rimraf": "^3.0.2", "set-value": "^4.1.0", "sha256-file": "1.0.0", - "shell-quote": "^1.7.3" + "shell-quote": "^1.7.4" }, "peerDependencies": { "serverless": "^2.32 || 3" diff --git a/test.js b/test.js index e2bbdc2c..27db6884 100644 --- a/test.js +++ b/test.js @@ -164,10 +164,6 @@ const getPythonBin = (version) => { return bin; }; -const hasPython = (version) => { - return Boolean(availablePythons[String(version)]); -}; - const listZipFiles = async function (filename) { const file = await readFile(filename); const zip = await new JSZip().loadAsync(file); @@ -227,57 +223,45 @@ test( { skip: !canUseDocker() || brokenOn('win32') } ); -test( - 'default pythonBin can package flask with default options', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 packages have the same hash', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const fileHash = sha256File('.serverless/sls-py-req-test.zip'); - sls(['package'], { env: {} }); - t.equal( - sha256File('.serverless/sls-py-req-test.zip'), - fileHash, - 'packages have the same hash' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 can package flask with default options', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { pythonBin: getPythonBin(3) } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !hasPython(3) } -); - -test( - 'py3.6 can package flask with hashes', +test('default pythonBin can package flask with default options', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.end(); +}); + +test('py3.7 packages have the same hash', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const fileHash = sha256File('.serverless/sls-py-req-test.zip'); + sls(['package'], { env: {} }); + t.equal( + sha256File('.serverless/sls-py-req-test.zip'), + fileHash, + 'packages have the same hash' + ); + t.end(); +}); + +test('py3.7 can package flask with default options', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { pythonBin: getPythonBin(3) } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.end(); +}); + +test( + 'py3.7 can package flask with hashes', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -292,150 +276,122 @@ test( t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); t.end(); }, - { skip: !hasPython(3) || brokenOn('win32') } -); - -test( - 'py3.6 can package flask with nested', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - fileName: 'requirements-w-nested.txt', - pythonBin: getPythonBin(3), - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !hasPython(3) } -); - -test( - 'py3.6 can package flask with zip option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { zip: 'true', pythonBin: getPythonBin(3) } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true( - zipfiles.includes('.requirements.zip'), - 'zipped requirements are packaged' - ); - t.true( - zipfiles.includes(`unzip_requirements.py`), - 'unzip util is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - "flask isn't packaged on its own" - ); - t.end(); - }, - { skip: !hasPython(3) } -); - -test( - 'py3.6 can package flask with slim option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { slim: 'true', pythonBin: getPythonBin(3) } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged' - ); - t.true( - zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > - 0, - '__main__.py files are packaged' - ); - t.end(); - }, - { skip: !hasPython(3) } -); - -test( - 'py3.6 can package flask with slim & slimPatterns options', - async (t) => { - process.chdir('tests/base'); - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { slim: 'true' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - "py3.6 doesn't package bottle with noDeploy option", - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - perl([ - '-p', - '-i.bak', - '-e', - 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', - 'serverless.yml', - ]); - sls(['package'], { env: { pythonBin: getPythonBin(3) } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.false(zipfiles.includes(`bottle.py`), 'bottle is NOT packaged'); - t.end(); - }, - { skip: !hasPython(3) } + { skip: brokenOn('win32') } ); -test( - 'py3.6 can package boto3 with editable', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - fileName: 'requirements-w-editable.txt', - pythonBin: getPythonBin(3), - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.true( - zipfiles.includes(`botocore${sep}__init__.py`), - 'botocore is packaged' - ); - t.end(); - }, - { skip: !hasPython(3) } -); +test('py3.7 can package flask with nested', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { + env: { + fileName: 'requirements-w-nested.txt', + pythonBin: getPythonBin(3), + }, + }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.end(); +}); + +test('py3.7 can package flask with zip option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { zip: 'true', pythonBin: getPythonBin(3) } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true( + zipfiles.includes('.requirements.zip'), + 'zipped requirements are packaged' + ); + t.true(zipfiles.includes(`unzip_requirements.py`), 'unzip util is packaged'); + t.false( + zipfiles.includes(`flask${sep}__init__.py`), + "flask isn't packaged on its own" + ); + t.end(); +}); + +test('py3.7 can package flask with slim option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { slim: 'true', pythonBin: getPythonBin(3) } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged' + ); + t.true( + zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > 0, + '__main__.py files are packaged' + ); + t.end(); +}); + +test('py3.7 can package flask with slim & slimPatterns options', async (t) => { + process.chdir('tests/base'); + copySync('_slimPatterns.yml', 'slimPatterns.yml'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { slim: 'true' } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged' + ); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('__main__.py')), + [], + '__main__.py files are NOT packaged' + ); + t.end(); +}); + +test("py3.7 doesn't package bottle with noDeploy option", async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + perl([ + '-p', + '-i.bak', + '-e', + 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', + 'serverless.yml', + ]); + sls(['package'], { env: { pythonBin: getPythonBin(3) } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.false(zipfiles.includes(`bottle.py`), 'bottle is NOT packaged'); + t.end(); +}); + +test('py3.7 can package boto3 with editable', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { + env: { + fileName: 'requirements-w-editable.txt', + pythonBin: getPythonBin(3), + }, + }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.true( + zipfiles.includes(`botocore${sep}__init__.py`), + 'botocore is packaged' + ); + t.end(); +}); test( - 'py3.6 can package flask with dockerizePip option', + 'py3.7 can package flask with dockerizePip option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -446,11 +402,11 @@ test( t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); test( - 'py3.6 can package flask with slim & dockerizePip option', + 'py3.7 can package flask with slim & dockerizePip option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -470,11 +426,11 @@ test( ); t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); test( - 'py3.6 can package flask with slim & dockerizePip & slimPatterns options', + 'py3.7 can package flask with slim & dockerizePip & slimPatterns options', async (t) => { process.chdir('tests/base'); copySync('_slimPatterns.yml', 'slimPatterns.yml'); @@ -495,11 +451,11 @@ test( ); t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); test( - 'py3.6 can package flask with zip & dockerizePip option', + 'py3.7 can package flask with zip & dockerizePip option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -527,11 +483,11 @@ test( ); t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); test( - 'py3.6 can package flask with zip & slim & dockerizePip option', + 'py3.7 can package flask with zip & slim & dockerizePip option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -561,101 +517,415 @@ test( ); t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); +test('pipenv py3.7 can package flask with default options', async (t) => { + process.chdir('tests/pipenv'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.false( + zipfiles.includes(`pytest${sep}__init__.py`), + 'dev-package pytest is NOT packaged' + ); + t.end(); +}); + +test('pipenv py3.7 can package flask with slim option', async (t) => { + process.chdir('tests/pipenv'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { slim: 'true' } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged' + ); + t.true( + zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > 0, + '__main__.py files are packaged' + ); + t.end(); +}); + +test('pipenv py3.7 can package flask with slim & slimPatterns options', async (t) => { + process.chdir('tests/pipenv'); + + copySync('_slimPatterns.yml', 'slimPatterns.yml'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { slim: 'true' } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged' + ); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('__main__.py')), + [], + '__main__.py files are NOT packaged' + ); + t.end(); +}); + +test('pipenv py3.7 can package flask with zip option', async (t) => { + process.chdir('tests/pipenv'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { zip: 'true', pythonBin: getPythonBin(3) } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true( + zipfiles.includes('.requirements.zip'), + 'zipped requirements are packaged' + ); + t.true(zipfiles.includes(`unzip_requirements.py`), 'unzip util is packaged'); + t.false( + zipfiles.includes(`flask${sep}__init__.py`), + "flask isn't packaged on its own" + ); + t.end(); +}); + +test("pipenv py3.7 doesn't package bottle with noDeploy option", async (t) => { + process.chdir('tests/pipenv'); + const path = npm(['pack', '../..']); + npm(['i', path]); + perl([ + '-p', + '-i.bak', + '-e', + 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', + 'serverless.yml', + ]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.false(zipfiles.includes(`bottle.py`), 'bottle is NOT packaged'); + t.end(); +}); + +test('non build pyproject.toml uses requirements.txt', async (t) => { + process.chdir('tests/non_build_pyproject'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.end(); +}); + +test('non poetry pyproject.toml without requirements.txt packages handler only', async (t) => { + process.chdir('tests/non_poetry_pyproject'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`handler.py`), 'handler is packaged'); + t.end(); +}); + +test('poetry py3.7 can package flask with default options', async (t) => { + process.chdir('tests/poetry'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`bottle.py`), 'bottle is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.end(); +}); + +test('poetry py3.7 can package flask with slim option', async (t) => { + process.chdir('tests/poetry'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { slim: 'true' } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged' + ); + t.true( + zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > 0, + '__main__.py files are packaged' + ); + t.end(); +}); + +test('poetry py3.7 can package flask with slim & slimPatterns options', async (t) => { + process.chdir('tests/poetry'); + + copySync('_slimPatterns.yml', 'slimPatterns.yml'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { slim: 'true' } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged' + ); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('__main__.py')), + [], + '__main__.py files are NOT packaged' + ); + t.end(); +}); + +test('poetry py3.7 can package flask with zip option', async (t) => { + process.chdir('tests/poetry'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { zip: 'true', pythonBin: getPythonBin(3) } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true( + zipfiles.includes('.requirements.zip'), + 'zipped requirements are packaged' + ); + t.true(zipfiles.includes(`unzip_requirements.py`), 'unzip util is packaged'); + t.false( + zipfiles.includes(`flask${sep}__init__.py`), + "flask isn't packaged on its own" + ); + t.end(); +}); + +test("poetry py3.7 doesn't package bottle with noDeploy option", async (t) => { + process.chdir('tests/poetry'); + const path = npm(['pack', '../..']); + npm(['i', path]); + perl([ + '-p', + '-i.bak', + '-e', + 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', + 'serverless.yml', + ]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.false(zipfiles.includes(`bottle.py`), 'bottle is NOT packaged'); + t.end(); +}); + +test('py3.7 can package flask with zip option and no explicit include', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + perl(['-p', '-i.bak', '-e', 's/include://', 'serverless.yml']); + perl(['-p', '-i.bak', '-e', 's/^.*handler.py.*$//', 'serverless.yml']); + sls(['package'], { env: { zip: 'true' } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true( + zipfiles.includes('.requirements.zip'), + 'zipped requirements are packaged' + ); + t.true(zipfiles.includes(`unzip_requirements.py`), 'unzip util is packaged'); + t.false( + zipfiles.includes(`flask${sep}__init__.py`), + "flask isn't packaged on its own" + ); + t.end(); +}); + +test('py3.7 can package lambda-decorators using vendor option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { vendor: './vendor' } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.true( + zipfiles.includes(`lambda_decorators.py`), + 'lambda_decorators.py is packaged' + ); + t.end(); +}); + test( - 'py2.7 can package flask with default options', + "Don't nuke execute perms", async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); + const perm = '755'; + npm(['i', path]); - sls(['package'], { - env: { runtime: 'python2.7', pythonBin: getPythonBin(2) }, - }); + perl([ + '-p', + '-i.bak', + '-e', + 's/(handler.py.*$)/$1\n - foobar/', + 'serverless.yml', + ]); + writeFileSync(`foobar`, ''); + chmodSync(`foobar`, perm); + sls(['package'], { env: { vendor: './vendor' } }); const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !hasPython(2) } -); + t.true( + zipfiles.includes(`lambda_decorators.py`), + 'lambda_decorators.py is packaged' + ); + t.true(zipfiles.includes(`foobar`), 'foobar is packaged'); -test( - 'py2.7 can package flask with slim option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { runtime: 'python2.7', slim: 'true', pythonBin: getPythonBin(2) }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged' + const zipfiles_with_metadata = await listZipFilesWithMetaData( + '.serverless/sls-py-req-test.zip' ); t.true( - zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > - 0, - '__main__.py files are packaged' + zipfiles_with_metadata['foobar'].unixPermissions + .toString(8) + .slice(3, 6) === perm, + 'foobar has retained its executable file permissions' + ); + + const flaskPerm = statSync('.serverless/requirements/bin/flask').mode; + t.true( + zipfiles_with_metadata['bin/flask'].unixPermissions === flaskPerm, + 'bin/flask has retained its executable file permissions' ); + t.end(); }, - { skip: !hasPython(2) } + { skip: process.platform === 'win32' } ); +test('py3.7 can package flask in a project with a space in it', async (t) => { + copySync('tests/base', 'tests/base with a space'); + process.chdir('tests/base with a space'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.end(); +}); + test( - 'py2.7 can package flask with zip option', + 'py3.7 can package flask in a project with a space in it with docker', async (t) => { - process.chdir('tests/base'); + copySync('tests/base', 'tests/base with a space'); + process.chdir('tests/base with a space'); const path = npm(['pack', '../..']); npm(['i', path]); - sls(['package'], { - env: { runtime: 'python2.7', zip: 'true', pythonBin: getPythonBin(2) }, - }); + sls(['package'], { env: { dockerizePip: 'true' } }); const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true( - zipfiles.includes('.requirements.zip'), - 'zipped requirements are packaged' - ); - t.true( - zipfiles.includes(`unzip_requirements.py`), - 'unzip util is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - "flask isn't packaged on its own" - ); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); t.end(); }, - { skip: !hasPython(2) } + { skip: !canUseDocker() || brokenOn('win32') } ); -test( - 'py2.7 can package flask with slim & dockerizePip & slimPatterns options', +test('py3.7 supports custom file name with fileName option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + writeFileSync('puck', 'requests'); + npm(['i', path]); + sls(['package'], { env: { fileName: 'puck' } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true( + zipfiles.includes(`requests${sep}__init__.py`), + 'requests is packaged' + ); + t.false(zipfiles.includes(`flask${sep}__init__.py`), 'flask is NOT packaged'); + t.false(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is NOT packaged'); + t.end(); +}); + +test("py3.7 doesn't package bottle with zip option", async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + perl([ + '-p', + '-i.bak', + '-e', + 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', + 'serverless.yml', + ]); + sls(['package'], { env: { zip: 'true', pythonBin: getPythonBin(3) } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + const zippedReqs = await listRequirementsZipFiles( + '.serverless/sls-py-req-test.zip' + ); + t.true( + zipfiles.includes('.requirements.zip'), + 'zipped requirements are packaged' + ); + t.true(zipfiles.includes(`unzip_requirements.py`), 'unzip util is packaged'); + t.false( + zipfiles.includes(`flask${sep}__init__.py`), + "flask isn't packaged on its own" + ); + t.true( + zippedReqs.includes(`flask/__init__.py`), + 'flask is packaged in the .requirements.zip file' + ); + t.false( + zippedReqs.includes(`bottle.py`), + 'bottle is NOT packaged in the .requirements.zip file' + ); + t.end(); +}); + +test('py3.7 can package flask with slim, slimPatterns & slimPatternsAppendDefaults=false options', async (t) => { + process.chdir('tests/base'); + copySync('_slimPatterns.yml', 'slimPatterns.yml'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { + env: { slim: 'true', slimPatternsAppendDefaults: 'false' }, + }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true( + zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, + 'pyc files are packaged' + ); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('__main__.py')), + [], + '__main__.py files are NOT packaged' + ); + t.end(); +}); + +test( + 'py3.7 can package flask with slim & dockerizePip & slimPatterns & slimPatternsAppendDefaults=false options', async (t) => { process.chdir('tests/base'); - copySync('_slimPatterns.yml', 'slimPatterns.yml'); const path = npm(['pack', '../..']); npm(['i', path]); sls(['package'], { env: { - runtime: 'python2.7', dockerizePip: 'true', slim: 'true', - pythonBin: getPythonBin(2), + slimPatternsAppendDefaults: 'false', }, }); const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - '*.pyc files are packaged' + t.true( + zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, + 'pyc files are packaged' ); t.deepEqual( zipfiles.filter((filename) => filename.endsWith('__main__.py')), @@ -664,1347 +934,371 @@ test( ); t.end(); }, - { skip: !canUseDocker() || !hasPython(2) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); +test('pipenv py3.7 can package flask with slim & slimPatterns & slimPatternsAppendDefaults=false option', async (t) => { + process.chdir('tests/pipenv'); + copySync('_slimPatterns.yml', 'slimPatterns.yml'); + const path = npm(['pack', '../..']); + npm(['i', path]); + + sls(['package'], { + env: { slim: 'true', slimPatternsAppendDefaults: 'false' }, + }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true( + zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, + 'pyc files are packaged' + ); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('__main__.py')), + [], + '__main__.py files are NOT packaged' + ); + t.end(); +}); + +test('poetry py3.7 can package flask with slim & slimPatterns & slimPatternsAppendDefaults=false option', async (t) => { + process.chdir('tests/poetry'); + copySync('_slimPatterns.yml', 'slimPatterns.yml'); + const path = npm(['pack', '../..']); + npm(['i', path]); + + sls(['package'], { + env: { slim: 'true', slimPatternsAppendDefaults: 'false' }, + }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true( + zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, + 'pyc files are packaged' + ); + t.deepEqual( + zipfiles.filter((filename) => filename.endsWith('__main__.py')), + [], + '__main__.py files are NOT packaged' + ); + t.end(); +}); + +test('poetry py3.7 can package flask with package individually option', async (t) => { + process.chdir('tests/poetry_individually'); + const path = npm(['pack', '../..']); + npm(['i', path]); + + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles( + '.serverless/module1-sls-py-req-test-dev-hello.zip' + ); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`bottle.py`), 'bottle is packaged'); + t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); + t.end(); +}); + +test('py3.7 can package flask with package individually option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { individually: 'true' } }); + const zipfiles_hello = await listZipFiles('.serverless/hello.zip'); + t.false( + zipfiles_hello.includes(`fn2${sep}__init__.py`), + 'fn2 is NOT packaged in function hello' + ); + t.true( + zipfiles_hello.includes('handler.py'), + 'handler.py is packaged in function hello' + ); + t.false( + zipfiles_hello.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello' + ); + t.true( + zipfiles_hello.includes(`flask${sep}__init__.py`), + 'flask is packaged in function hello' + ); + + const zipfiles_hello2 = await listZipFiles('.serverless/hello2.zip'); + t.false( + zipfiles_hello2.includes(`fn2${sep}__init__.py`), + 'fn2 is NOT packaged in function hello2' + ); + t.true( + zipfiles_hello2.includes('handler.py'), + 'handler.py is packaged in function hello2' + ); + t.false( + zipfiles_hello2.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello2' + ); + t.true( + zipfiles_hello2.includes(`flask${sep}__init__.py`), + 'flask is packaged in function hello2' + ); + + const zipfiles_hello3 = await listZipFiles('.serverless/hello3.zip'); + t.false( + zipfiles_hello3.includes(`fn2${sep}__init__.py`), + 'fn2 is NOT packaged in function hello3' + ); + t.true( + zipfiles_hello3.includes('handler.py'), + 'handler.py is packaged in function hello3' + ); + t.false( + zipfiles_hello3.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello3' + ); + t.false( + zipfiles_hello3.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello3' + ); + + const zipfiles_hello4 = await listZipFiles( + '.serverless/fn2-sls-py-req-test-dev-hello4.zip' + ); + t.false( + zipfiles_hello4.includes(`fn2${sep}__init__.py`), + 'fn2 is NOT packaged in function hello4' + ); + t.true( + zipfiles_hello4.includes('fn2_handler.py'), + 'fn2_handler is packaged in the zip-root in function hello4' + ); + t.true( + zipfiles_hello4.includes(`dataclasses.py`), + 'dataclasses is packaged in function hello4' + ); + t.false( + zipfiles_hello4.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello4' + ); + + t.end(); +}); + +test('py3.7 can package flask with package individually & slim option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { individually: 'true', slim: 'true' } }); + const zipfiles_hello = await listZipFiles('.serverless/hello.zip'); + t.true( + zipfiles_hello.includes('handler.py'), + 'handler.py is packaged in function hello' + ); + t.deepEqual( + zipfiles_hello.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged in function hello' + ); + t.true( + zipfiles_hello.includes(`flask${sep}__init__.py`), + 'flask is packaged in function hello' + ); + t.false( + zipfiles_hello.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello' + ); + + const zipfiles_hello2 = await listZipFiles('.serverless/hello2.zip'); + t.true( + zipfiles_hello2.includes('handler.py'), + 'handler.py is packaged in function hello2' + ); + t.deepEqual( + zipfiles_hello2.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged in function hello2' + ); + t.true( + zipfiles_hello2.includes(`flask${sep}__init__.py`), + 'flask is packaged in function hello2' + ); + t.false( + zipfiles_hello2.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello2' + ); + + const zipfiles_hello3 = await listZipFiles('.serverless/hello3.zip'); + t.true( + zipfiles_hello3.includes('handler.py'), + 'handler.py is packaged in function hello3' + ); + t.deepEqual( + zipfiles_hello3.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged in function hello3' + ); + t.false( + zipfiles_hello3.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello3' + ); + + const zipfiles_hello4 = await listZipFiles( + '.serverless/fn2-sls-py-req-test-dev-hello4.zip' + ); + t.true( + zipfiles_hello4.includes('fn2_handler.py'), + 'fn2_handler is packaged in the zip-root in function hello4' + ); + t.true( + zipfiles_hello4.includes(`dataclasses.py`), + 'dataclasses is packaged in function hello4' + ); + t.false( + zipfiles_hello4.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello4' + ); + t.deepEqual( + zipfiles_hello4.filter((filename) => filename.endsWith('.pyc')), + [], + 'no pyc files packaged in function hello4' + ); + + t.end(); +}); + +test('py3.7 can package only requirements of module', async (t) => { + process.chdir('tests/individually'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const zipfiles_hello = await listZipFiles( + '.serverless/module1-sls-py-req-test-indiv-dev-hello1.zip' + ); + t.true( + zipfiles_hello.includes('handler1.py'), + 'handler1.py is packaged at root level in function hello1' + ); + t.false( + zipfiles_hello.includes('handler2.py'), + 'handler2.py is NOT packaged at root level in function hello1' + ); + t.true( + zipfiles_hello.includes(`pyaml${sep}__init__.py`), + 'pyaml is packaged in function hello1' + ); + t.true( + zipfiles_hello.includes(`boto3${sep}__init__.py`), + 'boto3 is packaged in function hello1' + ); + t.false( + zipfiles_hello.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello1' + ); + + const zipfiles_hello2 = await listZipFiles( + '.serverless/module2-sls-py-req-test-indiv-dev-hello2.zip' + ); + t.true( + zipfiles_hello2.includes('handler2.py'), + 'handler2.py is packaged at root level in function hello2' + ); + t.false( + zipfiles_hello2.includes('handler1.py'), + 'handler1.py is NOT packaged at root level in function hello2' + ); + t.false( + zipfiles_hello2.includes(`pyaml${sep}__init__.py`), + 'pyaml is NOT packaged in function hello2' + ); + t.false( + zipfiles_hello2.includes(`boto3${sep}__init__.py`), + 'boto3 is NOT packaged in function hello2' + ); + t.true( + zipfiles_hello2.includes(`flask${sep}__init__.py`), + 'flask is packaged in function hello2' + ); + + t.end(); +}); + +test('py3.7 can package lambda-decorators using vendor and invidiually option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { individually: 'true', vendor: './vendor' } }); + const zipfiles_hello = await listZipFiles('.serverless/hello.zip'); + t.true( + zipfiles_hello.includes('handler.py'), + 'handler.py is packaged at root level in function hello' + ); + t.true( + zipfiles_hello.includes(`flask${sep}__init__.py`), + 'flask is packaged in function hello' + ); + t.true( + zipfiles_hello.includes(`lambda_decorators.py`), + 'lambda_decorators.py is packaged in function hello' + ); + t.false( + zipfiles_hello.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello' + ); + + const zipfiles_hello2 = await listZipFiles('.serverless/hello2.zip'); + t.true( + zipfiles_hello2.includes('handler.py'), + 'handler.py is packaged at root level in function hello2' + ); + t.true( + zipfiles_hello2.includes(`flask${sep}__init__.py`), + 'flask is packaged in function hello2' + ); + t.true( + zipfiles_hello2.includes(`lambda_decorators.py`), + 'lambda_decorators.py is packaged in function hello2' + ); + t.false( + zipfiles_hello2.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello2' + ); + + const zipfiles_hello3 = await listZipFiles('.serverless/hello3.zip'); + t.true( + zipfiles_hello3.includes('handler.py'), + 'handler.py is packaged at root level in function hello3' + ); + t.false( + zipfiles_hello3.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello3' + ); + t.false( + zipfiles_hello3.includes(`lambda_decorators.py`), + 'lambda_decorators.py is NOT packaged in function hello3' + ); + t.false( + zipfiles_hello3.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello3' + ); + + const zipfiles_hello4 = await listZipFiles( + '.serverless/fn2-sls-py-req-test-dev-hello4.zip' + ); + t.true( + zipfiles_hello4.includes('fn2_handler.py'), + 'fn2_handler is packaged in the zip-root in function hello4' + ); + t.true( + zipfiles_hello4.includes(`dataclasses.py`), + 'dataclasses is packaged in function hello4' + ); + t.false( + zipfiles_hello4.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello4' + ); + t.end(); +}); + test( - "py2.7 doesn't package bottle with noDeploy option", + "Don't nuke execute perms when using individually", async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - perl([ - '-p', - '-i.bak', - '-e', - 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', - 'serverless.yml', - ]); - sls(['package'], { - env: { runtime: 'python2.7', pythonBin: getPythonBin(2) }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.false(zipfiles.includes(`bottle.py`), 'bottle is NOT packaged'); - t.end(); - }, - { skip: !hasPython(2) } -); - -test( - 'py2.7 can package flask with zip & dockerizePip option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - runtime: 'python2.7', - dockerizePip: 'true', - zip: 'true', - pythonBin: getPythonBin(2), - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - const zippedReqs = await listRequirementsZipFiles( - '.serverless/sls-py-req-test.zip' - ); - t.true( - zipfiles.includes('.requirements.zip'), - 'zipped requirements are packaged' - ); - t.true( - zipfiles.includes(`unzip_requirements.py`), - 'unzip util is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - "flask isn't packaged on its own" - ); - t.true( - zippedReqs.includes(`flask/__init__.py`), - 'flask is packaged in the .requirements.zip file' - ); - t.end(); - }, - { skip: !canUseDocker() || !hasPython(2) || brokenOn('win32') } -); - -test( - 'py2.7 can package flask with zip & slim & dockerizePip option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - runtime: 'python2.7', - dockerizePip: 'true', - zip: 'true', - slim: 'true', - pythonBin: getPythonBin(2), - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - const zippedReqs = await listRequirementsZipFiles( - '.serverless/sls-py-req-test.zip' - ); - t.true( - zipfiles.includes('.requirements.zip'), - 'zipped requirements are packaged' - ); - t.true( - zipfiles.includes(`unzip_requirements.py`), - 'unzip util is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - "flask isn't packaged on its own" - ); - t.true( - zippedReqs.includes(`flask/__init__.py`), - 'flask is packaged in the .requirements.zip file' - ); - t.end(); - }, - { skip: !canUseDocker() || !hasPython(2) || brokenOn('win32') } -); - -test( - 'py2.7 can package flask with dockerizePip option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - runtime: 'python2.7', - dockerizePip: 'true', - pythonBin: getPythonBin(2), - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !canUseDocker() || !hasPython(2) || brokenOn('win32') } -); - -test( - 'py2.7 can package flask with slim & dockerizePip option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - runtime: 'python2.7', - dockerizePip: 'true', - slim: 'true', - pythonBin: getPythonBin(2), - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - '*.pyc files are NOT packaged' - ); - t.true( - zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > - 0, - '__main__.py files are packaged' - ); - t.end(); - }, - { skip: !canUseDocker() || !hasPython(2) || brokenOn('win32') } -); - -test( - 'py2.7 can package flask with slim & dockerizePip & slimPatterns options', - async (t) => { - process.chdir('tests/base'); - - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - runtime: 'python2.7', - dockerizePip: 'true', - slim: 'true', - pythonBin: getPythonBin(2), - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - '*.pyc files are packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !canUseDocker() || !hasPython(2) || brokenOn('win32') } -); - -test( - 'pipenv py3.6 can package flask with default options', - async (t) => { - process.chdir('tests/pipenv'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.false( - zipfiles.includes(`pytest${sep}__init__.py`), - 'dev-package pytest is NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'pipenv py3.6 can package flask with slim option', - async (t) => { - process.chdir('tests/pipenv'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { slim: 'true' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged' - ); - t.true( - zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > - 0, - '__main__.py files are packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'pipenv py3.6 can package flask with slim & slimPatterns options', - async (t) => { - process.chdir('tests/pipenv'); - - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { slim: 'true' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'pipenv py3.6 can package flask with zip option', - async (t) => { - process.chdir('tests/pipenv'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { zip: 'true', pythonBin: getPythonBin(3) } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true( - zipfiles.includes('.requirements.zip'), - 'zipped requirements are packaged' - ); - t.true( - zipfiles.includes(`unzip_requirements.py`), - 'unzip util is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - "flask isn't packaged on its own" - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - "pipenv py3.6 doesn't package bottle with noDeploy option", - async (t) => { - process.chdir('tests/pipenv'); - const path = npm(['pack', '../..']); - npm(['i', path]); - perl([ - '-p', - '-i.bak', - '-e', - 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', - 'serverless.yml', - ]); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.false(zipfiles.includes(`bottle.py`), 'bottle is NOT packaged'); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'non build pyproject.toml uses requirements.txt', - async (t) => { - process.chdir('tests/non_build_pyproject'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'non poetry pyproject.toml without requirements.txt packages handler only', - async (t) => { - process.chdir('tests/non_poetry_pyproject'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`handler.py`), 'handler is packaged'); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'poetry py3.6 can package flask with default options', - async (t) => { - process.chdir('tests/poetry'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`bottle.py`), 'bottle is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'poetry py3.6 can package flask with slim option', - async (t) => { - process.chdir('tests/poetry'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { slim: 'true' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged' - ); - t.true( - zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > - 0, - '__main__.py files are packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'poetry py3.6 can package flask with slim & slimPatterns options', - async (t) => { - process.chdir('tests/poetry'); - - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { slim: 'true' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'poetry py3.6 can package flask with zip option', - async (t) => { - process.chdir('tests/poetry'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { zip: 'true', pythonBin: getPythonBin(3) } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true( - zipfiles.includes('.requirements.zip'), - 'zipped requirements are packaged' - ); - t.true( - zipfiles.includes(`unzip_requirements.py`), - 'unzip util is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - "flask isn't packaged on its own" - ); - t.end(); - }, - { skip: !hasPython(3) } -); - -test( - "poetry py3.6 doesn't package bottle with noDeploy option", - async (t) => { - process.chdir('tests/poetry'); - const path = npm(['pack', '../..']); - npm(['i', path]); - perl([ - '-p', - '-i.bak', - '-e', - 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', - 'serverless.yml', - ]); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.false(zipfiles.includes(`bottle.py`), 'bottle is NOT packaged'); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 can package flask with zip option and no explicit include', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - perl(['-p', '-i.bak', '-e', 's/include://', 'serverless.yml']); - perl(['-p', '-i.bak', '-e', 's/^.*handler.py.*$//', 'serverless.yml']); - sls(['package'], { env: { zip: 'true' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true( - zipfiles.includes('.requirements.zip'), - 'zipped requirements are packaged' - ); - t.true( - zipfiles.includes(`unzip_requirements.py`), - 'unzip util is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - "flask isn't packaged on its own" - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 can package lambda-decorators using vendor option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { vendor: './vendor' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.true( - zipfiles.includes(`lambda_decorators.py`), - 'lambda_decorators.py is packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - "Don't nuke execute perms", - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - const perm = '755'; - - npm(['i', path]); - perl([ - '-p', - '-i.bak', - '-e', - 's/(handler.py.*$)/$1\n - foobar/', - 'serverless.yml', - ]); - writeFileSync(`foobar`, ''); - chmodSync(`foobar`, perm); - sls(['package'], { env: { vendor: './vendor' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.true( - zipfiles.includes(`lambda_decorators.py`), - 'lambda_decorators.py is packaged' - ); - t.true(zipfiles.includes(`foobar`), 'foobar is packaged'); - - const zipfiles_with_metadata = await listZipFilesWithMetaData( - '.serverless/sls-py-req-test.zip' - ); - t.true( - zipfiles_with_metadata['foobar'].unixPermissions - .toString(8) - .slice(3, 6) === perm, - 'foobar has retained its executable file permissions' - ); - - const flaskPerm = statSync('.serverless/requirements/bin/flask').mode; - t.true( - zipfiles_with_metadata['bin/flask'].unixPermissions === flaskPerm, - 'bin/flask has retained its executable file permissions' - ); - - t.end(); - }, - { skip: process.platform === 'win32' || !hasPython(3.6) } -); - -test( - 'py3.6 can package flask in a project with a space in it', - async (t) => { - copySync('tests/base', 'tests/base with a space'); - process.chdir('tests/base with a space'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 can package flask in a project with a space in it with docker', - async (t) => { - copySync('tests/base', 'tests/base with a space'); - process.chdir('tests/base with a space'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { dockerizePip: 'true' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } -); - -test( - 'py3.6 supports custom file name with fileName option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - writeFileSync('puck', 'requests'); - npm(['i', path]); - sls(['package'], { env: { fileName: 'puck' } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true( - zipfiles.includes(`requests${sep}__init__.py`), - 'requests is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged' - ); - t.false( - zipfiles.includes(`boto3${sep}__init__.py`), - 'boto3 is NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - "py3.6 doesn't package bottle with zip option", - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - perl([ - '-p', - '-i.bak', - '-e', - 's/(pythonRequirements:$)/\\1\\n noDeploy: [bottle]/', - 'serverless.yml', - ]); - sls(['package'], { env: { zip: 'true', pythonBin: getPythonBin(3) } }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - const zippedReqs = await listRequirementsZipFiles( - '.serverless/sls-py-req-test.zip' - ); - t.true( - zipfiles.includes('.requirements.zip'), - 'zipped requirements are packaged' - ); - t.true( - zipfiles.includes(`unzip_requirements.py`), - 'unzip util is packaged' - ); - t.false( - zipfiles.includes(`flask${sep}__init__.py`), - "flask isn't packaged on its own" - ); - t.true( - zippedReqs.includes(`flask/__init__.py`), - 'flask is packaged in the .requirements.zip file' - ); - t.false( - zippedReqs.includes(`bottle.py`), - 'bottle is NOT packaged in the .requirements.zip file' - ); - t.end(); - }, - { skip: !hasPython(3) } -); - -test( - 'py3.6 can package flask with slim, slimPatterns & slimPatternsAppendDefaults=false options', - async (t) => { - process.chdir('tests/base'); - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { slim: 'true', slimPatternsAppendDefaults: 'false' }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true( - zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, - 'pyc files are packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 can package flask with slim & dockerizePip & slimPatterns & slimPatternsAppendDefaults=false options', - async (t) => { - process.chdir('tests/base'); - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - dockerizePip: 'true', - slim: 'true', - slimPatternsAppendDefaults: 'false', - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true( - zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, - 'pyc files are packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } -); - -test( - 'py2.7 can package flask with slim & slimPatterns & slimPatternsAppendDefaults=false options', - async (t) => { - process.chdir('tests/base'); - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - runtime: 'python2.7', - slim: 'true', - slimPatternsAppendDefaults: 'false', - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true( - zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, - 'pyc files are packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(2.7) || brokenOn('win32') } -); - -test( - 'py2.7 can package flask with slim & dockerizePip & slimPatterns & slimPatternsAppendDefaults=false options', - async (t) => { - process.chdir('tests/base'); - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { - dockerizePip: 'true', - runtime: 'python2.7', - slim: 'true', - slimPatternsAppendDefaults: 'false', - }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true( - zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, - 'pyc files are packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !canUseDocker() || !hasPython(2.7) || brokenOn('win32') } -); - -test( - 'pipenv py3.6 can package flask with slim & slimPatterns & slimPatternsAppendDefaults=false option', - async (t) => { - process.chdir('tests/pipenv'); - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - - sls(['package'], { - env: { slim: 'true', slimPatternsAppendDefaults: 'false' }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true( - zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, - 'pyc files are packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'poetry py3.6 can package flask with slim & slimPatterns & slimPatternsAppendDefaults=false option', - async (t) => { - process.chdir('tests/poetry'); - copySync('_slimPatterns.yml', 'slimPatterns.yml'); - const path = npm(['pack', '../..']); - npm(['i', path]); - - sls(['package'], { - env: { slim: 'true', slimPatternsAppendDefaults: 'false' }, - }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true( - zipfiles.filter((filename) => filename.endsWith('.pyc')).length >= 1, - 'pyc files are packaged' - ); - t.deepEqual( - zipfiles.filter((filename) => filename.endsWith('__main__.py')), - [], - '__main__.py files are NOT packaged' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'poetry py3.6 can package flask with package individually option', - async (t) => { - process.chdir('tests/poetry_individually'); - const path = npm(['pack', '../..']); - npm(['i', path]); - - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles( - '.serverless/module1-sls-py-req-test-dev-hello.zip' - ); - t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`bottle.py`), 'bottle is packaged'); - t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 can package flask with package individually option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { individually: 'true' } }); - const zipfiles_hello = await listZipFiles('.serverless/hello.zip'); - t.false( - zipfiles_hello.includes(`fn2${sep}__init__.py`), - 'fn2 is NOT packaged in function hello' - ); - t.true( - zipfiles_hello.includes('handler.py'), - 'handler.py is packaged in function hello' - ); - t.false( - zipfiles_hello.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello' - ); - t.true( - zipfiles_hello.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello' - ); - - const zipfiles_hello2 = await listZipFiles('.serverless/hello2.zip'); - t.false( - zipfiles_hello2.includes(`fn2${sep}__init__.py`), - 'fn2 is NOT packaged in function hello2' - ); - t.true( - zipfiles_hello2.includes('handler.py'), - 'handler.py is packaged in function hello2' - ); - t.false( - zipfiles_hello2.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello2' - ); - t.true( - zipfiles_hello2.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello2' - ); - - const zipfiles_hello3 = await listZipFiles('.serverless/hello3.zip'); - t.false( - zipfiles_hello3.includes(`fn2${sep}__init__.py`), - 'fn2 is NOT packaged in function hello3' - ); - t.true( - zipfiles_hello3.includes('handler.py'), - 'handler.py is packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello3' - ); - - const zipfiles_hello4 = await listZipFiles( - '.serverless/fn2-sls-py-req-test-dev-hello4.zip' - ); - t.false( - zipfiles_hello4.includes(`fn2${sep}__init__.py`), - 'fn2 is NOT packaged in function hello4' - ); - t.true( - zipfiles_hello4.includes('fn2_handler.py'), - 'fn2_handler is packaged in the zip-root in function hello4' - ); - t.true( - zipfiles_hello4.includes(`dataclasses.py`), - 'dataclasses is packaged in function hello4' - ); - t.false( - zipfiles_hello4.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello4' - ); - - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 can package flask with package individually & slim option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { individually: 'true', slim: 'true' } }); - const zipfiles_hello = await listZipFiles('.serverless/hello.zip'); - t.true( - zipfiles_hello.includes('handler.py'), - 'handler.py is packaged in function hello' - ); - t.deepEqual( - zipfiles_hello.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged in function hello' - ); - t.true( - zipfiles_hello.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello' - ); - t.false( - zipfiles_hello.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello' - ); - - const zipfiles_hello2 = await listZipFiles('.serverless/hello2.zip'); - t.true( - zipfiles_hello2.includes('handler.py'), - 'handler.py is packaged in function hello2' - ); - t.deepEqual( - zipfiles_hello2.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged in function hello2' - ); - t.true( - zipfiles_hello2.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello2' - ); - t.false( - zipfiles_hello2.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello2' - ); - - const zipfiles_hello3 = await listZipFiles('.serverless/hello3.zip'); - t.true( - zipfiles_hello3.includes('handler.py'), - 'handler.py is packaged in function hello3' - ); - t.deepEqual( - zipfiles_hello3.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello3' - ); - - const zipfiles_hello4 = await listZipFiles( - '.serverless/fn2-sls-py-req-test-dev-hello4.zip' - ); - t.true( - zipfiles_hello4.includes('fn2_handler.py'), - 'fn2_handler is packaged in the zip-root in function hello4' - ); - t.true( - zipfiles_hello4.includes(`dataclasses.py`), - 'dataclasses is packaged in function hello4' - ); - t.false( - zipfiles_hello4.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello4' - ); - t.deepEqual( - zipfiles_hello4.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged in function hello4' - ); - - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py2.7 can package flask with package individually option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { individually: 'true', runtime: 'python2.7' } }); - const zipfiles_hello = await listZipFiles('.serverless/hello.zip'); - t.true( - zipfiles_hello.includes('handler.py'), - 'handler.py is packaged in function hello' - ); - t.true( - zipfiles_hello.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello' - ); - t.false( - zipfiles_hello.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello' - ); - - const zipfiles_hello2 = await listZipFiles('.serverless/hello2.zip'); - t.true( - zipfiles_hello2.includes('handler.py'), - 'handler.py is packaged in function hello2' - ); - t.true( - zipfiles_hello2.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello2' - ); - t.false( - zipfiles_hello2.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello2' - ); - - const zipfiles_hello3 = await listZipFiles('.serverless/hello3.zip'); - t.true( - zipfiles_hello3.includes('handler.py'), - 'handler.py is packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello3' - ); - - const zipfiles_hello4 = await listZipFiles( - '.serverless/fn2-sls-py-req-test-dev-hello4.zip' - ); - t.true( - zipfiles_hello4.includes('fn2_handler.py'), - 'fn2_handler is packaged in the zip-root in function hello4' - ); - t.true( - zipfiles_hello4.includes(`dataclasses.py`), - 'dataclasses is packaged in function hello4' - ); - t.false( - zipfiles_hello4.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello4' - ); - - t.end(); - }, - { skip: !hasPython(2.7) } -); - -test( - 'py2.7 can package flask with package individually & slim option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { - env: { individually: 'true', runtime: 'python2.7', slim: 'true' }, - }); - const zipfiles_hello = await listZipFiles('.serverless/hello.zip'); - t.true( - zipfiles_hello.includes('handler.py'), - 'handler.py is packaged in function hello' - ); - t.deepEqual( - zipfiles_hello.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged in function hello' - ); - t.true( - zipfiles_hello.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello' - ); - t.false( - zipfiles_hello.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello' - ); - - const zipfiles_hello2 = await listZipFiles('.serverless/hello2.zip'); - t.true( - zipfiles_hello2.includes('handler.py'), - 'handler.py is packaged in function hello2' - ); - t.deepEqual( - zipfiles_hello2.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged in function hello2' - ); - t.true( - zipfiles_hello2.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello2' - ); - t.false( - zipfiles_hello2.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello2' - ); - - const zipfiles_hello3 = await listZipFiles('.serverless/hello3.zip'); - t.true( - zipfiles_hello3.includes('handler.py'), - 'handler.py is packaged in function hello3' - ); - t.deepEqual( - zipfiles_hello3.filter((filename) => filename.endsWith('.pyc')), - [], - 'no pyc files packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello3' - ); - - const zipfiles_hello4 = await listZipFiles( - '.serverless/fn2-sls-py-req-test-dev-hello4.zip' - ); - t.true( - zipfiles_hello4.includes('fn2_handler.py'), - 'fn2_handler is packaged in the zip-root in function hello4' - ); - t.true( - zipfiles_hello4.includes(`dataclasses.py`), - 'dataclasses is packaged in function hello4' - ); - t.false( - zipfiles_hello4.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello4' - ); - - t.end(); - }, - { skip: !hasPython(2.7) } -); - -test( - 'py2.7 can ignore functions defined with `image`', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { individually: 'true', runtime: 'python2.7' } }); - t.true( - pathExistsSync('.serverless/hello.zip'), - 'function hello is packaged' - ); - t.true( - pathExistsSync('.serverless/hello2.zip'), - 'function hello2 is packaged' - ); - t.true( - pathExistsSync('.serverless/hello3.zip'), - 'function hello3 is packaged' - ); - t.true( - pathExistsSync('.serverless/hello4.zip'), - 'function hello4 is packaged' - ); - t.false( - pathExistsSync('.serverless/hello5.zip'), - 'function hello5 is not packaged' - ); - - t.end(); - }, - { skip: !hasPython(2.7) } -); - -test( - 'py3.6 can package only requirements of module', - async (t) => { - process.chdir('tests/individually'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const zipfiles_hello = await listZipFiles( - '.serverless/module1-sls-py-req-test-indiv-dev-hello1.zip' - ); - t.true( - zipfiles_hello.includes('handler1.py'), - 'handler1.py is packaged at root level in function hello1' - ); - t.false( - zipfiles_hello.includes('handler2.py'), - 'handler2.py is NOT packaged at root level in function hello1' - ); - t.true( - zipfiles_hello.includes(`pyaml${sep}__init__.py`), - 'pyaml is packaged in function hello1' - ); - t.true( - zipfiles_hello.includes(`boto3${sep}__init__.py`), - 'boto3 is packaged in function hello1' - ); - t.false( - zipfiles_hello.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello1' - ); - - const zipfiles_hello2 = await listZipFiles( - '.serverless/module2-sls-py-req-test-indiv-dev-hello2.zip' - ); - t.true( - zipfiles_hello2.includes('handler2.py'), - 'handler2.py is packaged at root level in function hello2' - ); - t.false( - zipfiles_hello2.includes('handler1.py'), - 'handler1.py is NOT packaged at root level in function hello2' - ); - t.false( - zipfiles_hello2.includes(`pyaml${sep}__init__.py`), - 'pyaml is NOT packaged in function hello2' - ); - t.false( - zipfiles_hello2.includes(`boto3${sep}__init__.py`), - 'boto3 is NOT packaged in function hello2' - ); - t.true( - zipfiles_hello2.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello2' - ); - - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 can package lambda-decorators using vendor and invidiually option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { individually: 'true', vendor: './vendor' } }); - const zipfiles_hello = await listZipFiles('.serverless/hello.zip'); - t.true( - zipfiles_hello.includes('handler.py'), - 'handler.py is packaged at root level in function hello' - ); - t.true( - zipfiles_hello.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello' - ); - t.true( - zipfiles_hello.includes(`lambda_decorators.py`), - 'lambda_decorators.py is packaged in function hello' - ); - t.false( - zipfiles_hello.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello' - ); - - const zipfiles_hello2 = await listZipFiles('.serverless/hello2.zip'); - t.true( - zipfiles_hello2.includes('handler.py'), - 'handler.py is packaged at root level in function hello2' - ); - t.true( - zipfiles_hello2.includes(`flask${sep}__init__.py`), - 'flask is packaged in function hello2' - ); - t.true( - zipfiles_hello2.includes(`lambda_decorators.py`), - 'lambda_decorators.py is packaged in function hello2' - ); - t.false( - zipfiles_hello2.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello2' - ); - - const zipfiles_hello3 = await listZipFiles('.serverless/hello3.zip'); - t.true( - zipfiles_hello3.includes('handler.py'), - 'handler.py is packaged at root level in function hello3' - ); - t.false( - zipfiles_hello3.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`lambda_decorators.py`), - 'lambda_decorators.py is NOT packaged in function hello3' - ); - t.false( - zipfiles_hello3.includes(`dataclasses.py`), - 'dataclasses is NOT packaged in function hello3' - ); - - const zipfiles_hello4 = await listZipFiles( - '.serverless/fn2-sls-py-req-test-dev-hello4.zip' - ); - t.true( - zipfiles_hello4.includes('fn2_handler.py'), - 'fn2_handler is packaged in the zip-root in function hello4' - ); - t.true( - zipfiles_hello4.includes(`dataclasses.py`), - 'dataclasses is packaged in function hello4' - ); - t.false( - zipfiles_hello4.includes(`flask${sep}__init__.py`), - 'flask is NOT packaged in function hello4' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - "Don't nuke execute perms when using individually", - async (t) => { - process.chdir('tests/individually'); + process.chdir('tests/individually'); const path = npm(['pack', '../..']); const perm = '755'; writeFileSync(`module1${sep}foobar`, ''); @@ -2037,7 +1331,7 @@ test( t.end(); }, - { skip: process.platform === 'win32' || !hasPython(3.6) } + { skip: process.platform === 'win32' } ); test( @@ -2076,44 +1370,36 @@ test( t.end(); }, - { skip: !canUseDocker() || process.platform === 'win32' || !hasPython(3.6) } + { skip: !canUseDocker() || process.platform === 'win32' } ); -test( - 'py3.6 uses download cache by default option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const cachepath = getUserCachePath(); - t.true( - pathExistsSync(`${cachepath}${sep}downloadCacheslspyc${sep}http`), - 'cache directory exists' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); +test('py3.7 uses download cache by default option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const cachepath = getUserCachePath(); + t.true( + pathExistsSync(`${cachepath}${sep}downloadCacheslspyc${sep}http`), + 'cache directory exists' + ); + t.end(); +}); -test( - 'py3.6 uses download cache by default', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { cacheLocation: '.requirements-cache' } }); - t.true( - pathExistsSync(`.requirements-cache${sep}downloadCacheslspyc${sep}http`), - 'cache directory exists' - ); - t.end(); - }, - { skip: !hasPython(3.6) } -); +test('py3.7 uses download cache by default', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { cacheLocation: '.requirements-cache' } }); + t.true( + pathExistsSync(`.requirements-cache${sep}downloadCacheslspyc${sep}http`), + 'cache directory exists' + ); + t.end(); +}); test( - 'py3.6 uses download cache with dockerizePip option', + 'py3.7 uses download cache with dockerizePip option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -2126,11 +1412,11 @@ test( ); t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); test( - 'py3.6 uses download cache with dockerizePip by default option', + 'py3.7 uses download cache with dockerizePip by default option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -2144,36 +1430,32 @@ test( ); t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } -); - -test( - 'py3.6 uses static and download cache', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const cachepath = getUserCachePath(); - const cacheFolderHash = sha256Path('.serverless/requirements.txt'); - const arch = 'x86_64'; - t.true( - pathExistsSync(`${cachepath}${sep}downloadCacheslspyc${sep}http`), - 'http exists in download-cache' - ); - t.true( - pathExistsSync( - `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}flask` - ), - 'flask exists in static-cache' - ); - t.end(); - }, - { skip: !hasPython(3.6) } + { skip: !canUseDocker() || brokenOn('win32') } ); -test( - 'py3.6 uses static and download cache with dockerizePip option', +test('py3.7 uses static and download cache', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const cachepath = getUserCachePath(); + const cacheFolderHash = sha256Path('.serverless/requirements.txt'); + const arch = 'x86_64'; + t.true( + pathExistsSync(`${cachepath}${sep}downloadCacheslspyc${sep}http`), + 'http exists in download-cache' + ); + t.true( + pathExistsSync( + `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}flask` + ), + 'flask exists in static-cache' + ); + t.end(); +}); + +test( + 'py3.7 uses static and download cache with dockerizePip option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -2194,78 +1476,70 @@ test( ); t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } -); - -test( - 'py3.6 uses static cache', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: {} }); - const cachepath = getUserCachePath(); - const cacheFolderHash = sha256Path('.serverless/requirements.txt'); - const arch = 'x86_64'; - t.true( - pathExistsSync( - `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}flask` - ), - 'flask exists in static-cache' - ); - t.true( - pathExistsSync( - `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}.completed_requirements` - ), - '.completed_requirements exists in static-cache' - ); - - // py3.6 checking that static cache actually pulls from cache (by poisoning it) - writeFileSync( - `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}injected_file_is_bad_form`, - 'injected new file into static cache folder' - ); - sls(['package'], { env: {} }); - const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true( - zipfiles.includes('injected_file_is_bad_form'), - "static cache is really used when running 'sls package' again" - ); - - t.end(); - }, - { skip: !hasPython(3.6) } -); - -test( - 'py3.6 uses static cache with cacheLocation option', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - const cachepath = '.requirements-cache'; - sls(['package'], { env: { cacheLocation: cachepath } }); - const cacheFolderHash = sha256Path('.serverless/requirements.txt'); - const arch = 'x86_64'; - t.true( - pathExistsSync( - `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}flask` - ), - 'flask exists in static-cache' - ); - t.true( - pathExistsSync( - `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}.completed_requirements` - ), - '.completed_requirements exists in static-cache' - ); - t.end(); - }, - { skip: !hasPython(3.6) } + { skip: !canUseDocker() || brokenOn('win32') } ); -test( - 'py3.6 uses static cache with dockerizePip & slim option', +test('py3.7 uses static cache', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const cachepath = getUserCachePath(); + const cacheFolderHash = sha256Path('.serverless/requirements.txt'); + const arch = 'x86_64'; + t.true( + pathExistsSync( + `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}flask` + ), + 'flask exists in static-cache' + ); + t.true( + pathExistsSync( + `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}.completed_requirements` + ), + '.completed_requirements exists in static-cache' + ); + + // py3.7 checking that static cache actually pulls from cache (by poisoning it) + writeFileSync( + `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}injected_file_is_bad_form`, + 'injected new file into static cache folder' + ); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true( + zipfiles.includes('injected_file_is_bad_form'), + "static cache is really used when running 'sls package' again" + ); + + t.end(); +}); + +test('py3.7 uses static cache with cacheLocation option', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + const cachepath = '.requirements-cache'; + sls(['package'], { env: { cacheLocation: cachepath } }); + const cacheFolderHash = sha256Path('.serverless/requirements.txt'); + const arch = 'x86_64'; + t.true( + pathExistsSync( + `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}flask` + ), + 'flask exists in static-cache' + ); + t.true( + pathExistsSync( + `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}.completed_requirements` + ), + '.completed_requirements exists in static-cache' + ); + t.end(); +}); + +test( + 'py3.7 uses static cache with dockerizePip & slim option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -2287,7 +1561,7 @@ test( '.completed_requirements exists in static-cache' ); - // py3.6 checking that static cache actually pulls from cache (by poisoning it) + // py3.7 checking that static cache actually pulls from cache (by poisoning it) writeFileSync( `${cachepath}${sep}${cacheFolderHash}_${arch}_slspyc${sep}injected_file_is_bad_form`, 'injected new file into static cache folder' @@ -2306,11 +1580,11 @@ test( t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); test( - 'py3.6 uses download cache with dockerizePip & slim option', + 'py3.7 uses download cache with dockerizePip & slim option', async (t) => { process.chdir('tests/base'); const path = npm(['pack', '../..']); @@ -2332,38 +1606,51 @@ test( t.end(); }, - { skip: !canUseDocker() || !hasPython(3.6) || brokenOn('win32') } + { skip: !canUseDocker() || brokenOn('win32') } ); -test( - 'py3.6 can ignore functions defined with `image`', - async (t) => { - process.chdir('tests/base'); - const path = npm(['pack', '../..']); - npm(['i', path]); - sls(['package'], { env: { individually: 'true' } }); - t.true( - pathExistsSync('.serverless/hello.zip'), - 'function hello is packaged' - ); - t.true( - pathExistsSync('.serverless/hello2.zip'), - 'function hello2 is packaged' - ); - t.true( - pathExistsSync('.serverless/hello3.zip'), - 'function hello3 is packaged' - ); - t.true( - pathExistsSync('.serverless/hello4.zip'), - 'function hello4 is packaged' - ); - t.false( - pathExistsSync('.serverless/hello5.zip'), - 'function hello5 is not packaged' - ); - - t.end(); - }, - { skip: !hasPython(3.6) } -); +test('py3.7 can ignore functions defined with `image`', async (t) => { + process.chdir('tests/base'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { individually: 'true' } }); + t.true(pathExistsSync('.serverless/hello.zip'), 'function hello is packaged'); + t.true( + pathExistsSync('.serverless/hello2.zip'), + 'function hello2 is packaged' + ); + t.true( + pathExistsSync('.serverless/hello3.zip'), + 'function hello3 is packaged' + ); + t.true( + pathExistsSync('.serverless/hello4.zip'), + 'function hello4 is packaged' + ); + t.false( + pathExistsSync('.serverless/hello5.zip'), + 'function hello5 is not packaged' + ); + + t.end(); +}); + +test('poetry py3.7 fails packaging if poetry.lock is missing and flag requirePoetryLockFile is set to true', async (t) => { + copySync('tests/poetry', 'tests/base with a space'); + process.chdir('tests/base with a space'); + removeSync('poetry.lock'); + + const path = npm(['pack', '../..']); + npm(['i', path]); + const stdout = sls(['package'], { + env: { requirePoetryLockFile: 'true', slim: 'true' }, + noThrow: true, + }); + t.true( + stdout.includes( + 'poetry.lock file not found - set requirePoetryLockFile to false to disable this error' + ), + 'flag works and error is properly reported' + ); + t.end(); +}); diff --git a/tests/base/requirements-w-nested.txt b/tests/base/requirements-w-nested.txt index 4d73c837..b09aa52a 100644 --- a/tests/base/requirements-w-nested.txt +++ b/tests/base/requirements-w-nested.txt @@ -1,3 +1,3 @@ -flask +flask==2.0.3 bottle -r requirements-common.txt diff --git a/tests/base/serverless.yml b/tests/base/serverless.yml index 6526246c..ef48e901 100644 --- a/tests/base/serverless.yml +++ b/tests/base/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test provider: name: aws - runtime: ${env:runtime, 'python3.6'} + runtime: ${env:runtime, 'python3.7'} plugins: - serverless-python-requirements diff --git a/tests/individually/module2/requirements.txt b/tests/individually/module2/requirements.txt index 7e106024..c09d0264 100644 --- a/tests/individually/module2/requirements.txt +++ b/tests/individually/module2/requirements.txt @@ -1 +1 @@ -flask +flask==2.0.3 diff --git a/tests/individually/serverless.yml b/tests/individually/serverless.yml index a83ac7e0..d73d613a 100644 --- a/tests/individually/serverless.yml +++ b/tests/individually/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test-indiv provider: name: aws - runtime: python3.6 + runtime: python3.7 package: individually: true diff --git a/tests/non_build_pyproject/requirements.txt b/tests/non_build_pyproject/requirements.txt index aa55d989..09764fc3 100644 --- a/tests/non_build_pyproject/requirements.txt +++ b/tests/non_build_pyproject/requirements.txt @@ -1,2 +1,2 @@ -flask +flask==2.0.3 boto3 diff --git a/tests/non_build_pyproject/serverless.yml b/tests/non_build_pyproject/serverless.yml index 02e5a1f3..b0436e61 100644 --- a/tests/non_build_pyproject/serverless.yml +++ b/tests/non_build_pyproject/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test provider: name: aws - runtime: python3.6 + runtime: python3.7 plugins: - serverless-python-requirements diff --git a/tests/non_poetry_pyproject/serverless.yml b/tests/non_poetry_pyproject/serverless.yml index 3d872a87..2b16790c 100644 --- a/tests/non_poetry_pyproject/serverless.yml +++ b/tests/non_poetry_pyproject/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test provider: name: aws - runtime: python3.6 + runtime: python3.7 plugins: - serverless-python-requirements diff --git a/tests/pipenv/Pipfile b/tests/pipenv/Pipfile index 0d65eb75..30e51dda 100644 --- a/tests/pipenv/Pipfile +++ b/tests/pipenv/Pipfile @@ -1,9 +1,10 @@ [[source]] -url = "https://pypi.python.org/simple" +url = "https://pypi.org/simple" verify_ssl = true +name = "pypi" [packages] -Flask = "*" +Flask = "==2.0.3" bottle = "*" boto3 = "*" diff --git a/tests/pipenv/serverless.yml b/tests/pipenv/serverless.yml index 4b343bfc..315f6741 100644 --- a/tests/pipenv/serverless.yml +++ b/tests/pipenv/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test provider: name: aws - runtime: python3.6 + runtime: python3.7 plugins: - serverless-python-requirements diff --git a/tests/poetry/serverless.yml b/tests/poetry/serverless.yml index 4b343bfc..2d032acd 100644 --- a/tests/poetry/serverless.yml +++ b/tests/poetry/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test provider: name: aws - runtime: python3.6 + runtime: python3.7 plugins: - serverless-python-requirements @@ -13,6 +13,7 @@ custom: slimPatterns: ${file(./slimPatterns.yml):slimPatterns, self:custom.defaults.slimPatterns} slimPatternsAppendDefaults: ${env:slimPatternsAppendDefaults, self:custom.defaults.slimPatternsAppendDefaults} dockerizePip: ${env:dockerizePip, self:custom.defaults.dockerizePip} + requirePoetryLockFile: ${env:requirePoetryLockFile, false} defaults: zip: false slimPatterns: false diff --git a/tests/poetry_individually/serverless.yml b/tests/poetry_individually/serverless.yml index 2cb2d160..527a2846 100644 --- a/tests/poetry_individually/serverless.yml +++ b/tests/poetry_individually/serverless.yml @@ -2,7 +2,7 @@ service: sls-py-req-test provider: name: aws - runtime: python3.6 + runtime: python3.7 plugins: - serverless-python-requirements