diff options
author | 2023-04-23 01:27:27 +0200 | |
---|---|---|
committer | 2023-04-23 01:27:27 +0200 | |
commit | 2477ce1f715dc906376472e9e5e26be66a072962 (patch) | |
tree | 1def3225ffce7925dd998ae426aa41c21e84a31c | |
parent | 86e6ac996030d478b32e69cb560af156bafbcae3 (diff) | |
download | bitbake-contrib-alpianon/srctrace2.tar.gz |
fetch2: Add tests for upstream source tracingalpianon/srctrace2
This patch adds test data and test cases for TraceUnpack.
Test data consists of expected output json files for a set of
real-world recipes presenting one or more peculiarities that
need to be correctly handled.
Signed-off-by: Alberto Pianon <alberto@pianon.eu>
-rwxr-xr-x | bin/bitbake-selftest | 1 | ||||
-rw-r--r-- | lib/bb/tests/trace-testdata/bzip2-1.0.8.unpack.trace.json.zst | bin | 0 -> 5861 bytes | |||
-rw-r--r-- | lib/bb/tests/trace-testdata/gettext-minimal-native-0.21.unpack.trace.json.zst | bin | 0 -> 892 bytes | |||
-rw-r--r-- | lib/bb/tests/trace-testdata/gosu-1.14.unpack.trace.json.zst | bin | 0 -> 45308 bytes | |||
-rw-r--r-- | lib/bb/tests/trace-testdata/npm-shrinkwrap-test.json | 307 | ||||
-rw-r--r-- | lib/bb/tests/trace-testdata/ot-br-posix-0.3.0+git083fb6921903441bf44f46c263c123eb4af6e4a9.unpack.trace.json.zst | bin | 0 -> 208758 bytes | |||
-rw-r--r-- | lib/bb/tests/trace-testdata/python3-cryptography-37.0.4.unpack.trace.json.zst | bin | 0 -> 180908 bytes | |||
-rw-r--r-- | lib/bb/tests/trace-testdata/snappy-1.1.9.unpack.trace.json.zst | bin | 0 -> 17511 bytes | |||
-rw-r--r-- | lib/bb/tests/trace-testdata/systemd-251.8.unpack.trace.json.zst | bin | 0 -> 179607 bytes | |||
-rw-r--r-- | lib/bb/tests/trace-testdata/test_npm-1.0.0.unpack.trace.json.zst | bin | 0 -> 18911 bytes | |||
-rw-r--r-- | lib/bb/tests/trace.py | 545 |
11 files changed, 853 insertions, 0 deletions
diff --git a/bin/bitbake-selftest b/bin/bitbake-selftest index 6d60a5d2..139debe1 100755 --- a/bin/bitbake-selftest +++ b/bin/bitbake-selftest @@ -32,6 +32,7 @@ tests = ["bb.tests.codeparser", "bb.tests.siggen", "bb.tests.utils", "bb.tests.trace_base", + "bb.tests.trace", "bb.tests.compression", "hashserv.tests", "layerindexlib.tests.layerindexobj", diff --git a/lib/bb/tests/trace-testdata/bzip2-1.0.8.unpack.trace.json.zst b/lib/bb/tests/trace-testdata/bzip2-1.0.8.unpack.trace.json.zst Binary files differnew file mode 100644 index 00000000..9e32119d --- /dev/null +++ b/lib/bb/tests/trace-testdata/bzip2-1.0.8.unpack.trace.json.zst diff --git a/lib/bb/tests/trace-testdata/gettext-minimal-native-0.21.unpack.trace.json.zst b/lib/bb/tests/trace-testdata/gettext-minimal-native-0.21.unpack.trace.json.zst Binary files differnew file mode 100644 index 00000000..bfcaa683 --- /dev/null +++ b/lib/bb/tests/trace-testdata/gettext-minimal-native-0.21.unpack.trace.json.zst diff --git a/lib/bb/tests/trace-testdata/gosu-1.14.unpack.trace.json.zst b/lib/bb/tests/trace-testdata/gosu-1.14.unpack.trace.json.zst Binary files differnew file mode 100644 index 00000000..801601c0 --- /dev/null +++ b/lib/bb/tests/trace-testdata/gosu-1.14.unpack.trace.json.zst diff --git a/lib/bb/tests/trace-testdata/npm-shrinkwrap-test.json b/lib/bb/tests/trace-testdata/npm-shrinkwrap-test.json new file mode 100644 index 00000000..59a1042d --- /dev/null +++ b/lib/bb/tests/trace-testdata/npm-shrinkwrap-test.json @@ -0,0 +1,307 @@ +{ + "name": "test_npm", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "file-renamer": { + "version": "git+https://github.com/pbarabolkin/node-file-renamer.git#ac02bfa928658ad7c9c9f13452e7884eb4564542", + "from": "git+https://github.com/pbarabolkin/node-file-renamer.git#ac02bfa928658ad7c9c9f13452e7884eb4564542", + "requires": { + "fs": "^0.0.1-security", + "glob": "^10.1.0", + "optimist": "^0.6.1", + "path": "^0.12.7" + }, + "dependencies": { + "fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, + "glob": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.1.0.tgz", + "integrity": "sha512-daGobsYuT0G4hng24B5LbeLNvwKZYRhWyDl3RvqqAGZjJnCopWWK6PWnAGBY1M/vdA63QE+jddhZcYp+74Bq6Q==", + "requires": { + "fs.realpath": "^1.0.0", + "minimatch": "^9.0.0", + "minipass": "^5.0.0", + "path-scurry": "^1.7.0" + }, + "dependencies": { + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "requires": { + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + } + } + } + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + }, + "path-scurry": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", + "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "requires": { + "lru-cache": "^9.0.0", + "minipass": "^5.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.0.3.tgz", + "integrity": "sha512-cyjNRew29d4kbgnz1sjDqxg7qg8NW4s+HQzCGjeon7DV5T2yDije16W9HaUFV1dhVEMh+SjrOcK0TomBmf3Egg==" + } + } + } + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==" + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==" + } + } + }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + }, + "dependencies": { + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + } + } + } + } + } + } + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + }, + "dependencies": { + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + } + } + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + }, + "dependencies": { + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "requires": { + "resolve": "^1.1.6" + }, + "dependencies": { + "resolve": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.3.tgz", + "integrity": "sha512-P8ur/gp/AmbEzjr729bZnLjXK5Z+4P0zhIJgBgzqRih7hL7BOukHGtSTA3ACMY467GRFz3duQsi0bDZdR7DKdw==", + "requires": { + "is-core-module": "^2.12.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "dependencies": { + "is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "requires": { + "has": "^1.0.3" + }, + "dependencies": { + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + }, + "dependencies": { + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + } + } + } + } + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + } + } + } + } + } + } + } + } + } diff --git a/lib/bb/tests/trace-testdata/ot-br-posix-0.3.0+git083fb6921903441bf44f46c263c123eb4af6e4a9.unpack.trace.json.zst b/lib/bb/tests/trace-testdata/ot-br-posix-0.3.0+git083fb6921903441bf44f46c263c123eb4af6e4a9.unpack.trace.json.zst Binary files differnew file mode 100644 index 00000000..284d1b94 --- /dev/null +++ b/lib/bb/tests/trace-testdata/ot-br-posix-0.3.0+git083fb6921903441bf44f46c263c123eb4af6e4a9.unpack.trace.json.zst diff --git a/lib/bb/tests/trace-testdata/python3-cryptography-37.0.4.unpack.trace.json.zst b/lib/bb/tests/trace-testdata/python3-cryptography-37.0.4.unpack.trace.json.zst Binary files differnew file mode 100644 index 00000000..066093c2 --- /dev/null +++ b/lib/bb/tests/trace-testdata/python3-cryptography-37.0.4.unpack.trace.json.zst diff --git a/lib/bb/tests/trace-testdata/snappy-1.1.9.unpack.trace.json.zst b/lib/bb/tests/trace-testdata/snappy-1.1.9.unpack.trace.json.zst Binary files differnew file mode 100644 index 00000000..1c78bd0c --- /dev/null +++ b/lib/bb/tests/trace-testdata/snappy-1.1.9.unpack.trace.json.zst diff --git a/lib/bb/tests/trace-testdata/systemd-251.8.unpack.trace.json.zst b/lib/bb/tests/trace-testdata/systemd-251.8.unpack.trace.json.zst Binary files differnew file mode 100644 index 00000000..bc99b12d --- /dev/null +++ b/lib/bb/tests/trace-testdata/systemd-251.8.unpack.trace.json.zst diff --git a/lib/bb/tests/trace-testdata/test_npm-1.0.0.unpack.trace.json.zst b/lib/bb/tests/trace-testdata/test_npm-1.0.0.unpack.trace.json.zst Binary files differnew file mode 100644 index 00000000..3c8b1524 --- /dev/null +++ b/lib/bb/tests/trace-testdata/test_npm-1.0.0.unpack.trace.json.zst diff --git a/lib/bb/tests/trace.py b/lib/bb/tests/trace.py new file mode 100644 index 00000000..489de854 --- /dev/null +++ b/lib/bb/tests/trace.py @@ -0,0 +1,545 @@ + +# Copyright (C) 2023 Alberto Pianon <pianon@array.eu> +# +# SPDX-License-Identifier: GPL-2.0-only +# + +import os +import re +import json +import shutil +import unittest +import tempfile +from pathlib import Path +import subprocess + +import bb + +from bb.tests.trace_base import create_file + +def skipIfNoNetwork(): + if os.environ.get("BB_SKIP_NETTESTS") == "yes": + return unittest.skip("network test") + return lambda f: f + +class SplitVarValueTest(unittest.TestCase): + + def test_split_var_value_with_items_without_spaces(self): + items_without_spaces = [ + "git://github.com/systemd/systemd-stable.git;protocol=https;branch=${SRCBRANCH}", + "${SRC_URI_MUSL}", + "file://0001-Adjust-for-musl-headers.patch" + ] + var_value = " ".join(items_without_spaces) + self.assertEqual( + bb.fetch2.trace.split_var_value(var_value, False), items_without_spaces) + + def test_split_var_value_with_items_with_spaces(self): + items_with_spaces = [ + "https://github.com/shadow-maint/shadow/releases/download/v${PV}/${BP}.tar.gz", + "${@bb.utils.contains('PACKAGECONFIG', 'pam', '${PAM_SRC_URI}', '', d)}", + "file://shadow-relaxed-usernames.patch", + ] + var_value = " ".join(items_with_spaces) + self.assertEqual( + bb.fetch2.trace.split_var_value(var_value, False), items_with_spaces) + + +class GetUnexpSrcUriTest(unittest.TestCase): + + def test_get_unexp_src_uri(self): + d = bb.data.init() + d.setVar("SRCBRANCH", "main") + d.setVar("SRC_URI", """ + git://github.com/systemd/systemd-stable.git;protocol=https;branch=${SRCBRANCH} + file://0001-Adjust-for-musl-headers.patch + """) + src_uri = "git://github.com/systemd/systemd-stable.git;protocol=https;branch=main" + unexp_src_uri = "git://github.com/systemd/systemd-stable.git;protocol=https;branch=${SRCBRANCH}" + self.assertEqual( + bb.fetch2.trace.get_unexp_src_uri(src_uri, d), unexp_src_uri) + + def test_get_unexp_src_uri_that_expands_to_multiple_items(self): + d = bb.data.init() + d.setVar("SRC_URI_MUSL", """ + file://0003-missing_type.h-add-comparison_fn_t.patch + file://0004-add-fallback-parse_printf_format-implementation.patch + file://0005-src-basic-missing.h-check-for-missing-strndupa.patch + """) + d.setVar("SRC_URI", """ + git://github.com/systemd/systemd-stable.git;protocol=https;branch=main + ${SRC_URI_MUSL} + file://0001-Adjust-for-musl-headers.patch + """) + src_uris = [ + "file://0003-missing_type.h-add-comparison_fn_t.patch", + "file://0004-add-fallback-parse_printf_format-implementation.patch", + "file://0005-src-basic-missing.h-check-for-missing-strndupa.patch", + ] + unexp_src_uri = "${SRC_URI_MUSL}" + for src_uri in src_uris: + self.assertEqual( + bb.fetch2.trace.get_unexp_src_uri(src_uri, d), unexp_src_uri) + + +class GetCleanSrcUriTest(unittest.TestCase): + + def test_get_clean_src_uri_from_src_uri_with_abs_path_in_param(self): + src_uris = { + "git://git.example.com/foo/foo-plugin1.git;destsuffix=/home/user/poky/build/tmp/work/core2-64-poky-linux/foo/0.0.1/foo-0.0.1/plugins/1;name=plugin1;protocol=https" : + "git://git.example.com/foo/foo-plugin1.git;destsuffix=<local-path>;name=plugin1;protocol=https", + "git://git.example.com/foo/foo-plugin1.git;name=plugin1;protocol=https;destsuffix=/home/user/poky/build/tmp/work/core2-64-poky-linux/foo/0.0.1/foo-0.0.1/plugins/1" : + "git://git.example.com/foo/foo-plugin1.git;name=plugin1;protocol=https;destsuffix=<local-path>" + } + for src_uri, clean_src_uri in src_uris.items(): + self.assertEqual( + bb.fetch2.trace.get_clean_src_uri(src_uri), clean_src_uri) + + def test_get_clean_src_uri_from_src_uri_with_abs_path_in_url_path(self): + src_uris = { + "file:///home/user/meta-foo/foo/foo_fix.patch;subdir=foo": + "file://<local-path>;subdir=foo", + "npmsw:///home/user/meta-example/npm-shrinkwrap.json": + "npmsw://<local-path>" + } + for src_uri, clean_src_uri in src_uris.items(): + self.assertEqual( + bb.fetch2.trace.get_clean_src_uri(src_uri), clean_src_uri) + +class GetDownloadLocationAndRelpathTest(unittest.TestCase): + + # TODO add test with a git remote pointing to repo tool manifest + + def test_get_dl_loc_for_dir_in_git_repo(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + + create_file(tmpdir / "repo/README", "hello") + create_file(tmpdir / "repo/doc/help.txt", "help") + git_dir = tmpdir/"repo" + subprocess.check_output(["git", "init"], cwd=git_dir) + subprocess.check_output(["git", "add", "-A"], cwd=git_dir) + subprocess.check_output(["git", "commit", "-m", "'initial commit'"], cwd=git_dir) + head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=git_dir).decode().strip("\n") + download_location, relpath = bb.fetch2.trace.get_dl_loc(tmpdir/"repo/doc") + self.assertEqual((download_location, relpath), (None, None)) # no origin + + os.rename(tmpdir/"repo/.git", tmpdir/"repo.git") + subprocess.check_output(["rm", "-Rf", "repo"], cwd=tmpdir) + subprocess.check_output(["git", "clone", "repo.git"], cwd=tmpdir, stderr=subprocess.DEVNULL) + download_location, relpath = bb.fetch2.trace.get_dl_loc(tmpdir/"repo/doc") + self.assertEqual(download_location, "git+%s@%s" % (tmpdir/"repo.git", head)) + self.assertEqual(relpath, "doc") + + download_location, relpath = bb.fetch2.trace.get_dl_loc(git_dir) + self.assertEqual(download_location, "git+%s@%s" % (tmpdir/"repo.git", head)) + self.assertEqual(relpath, "") + + create_file(tmpdir/"repo/LICENSE", "CC-0") + subprocess.check_output(["git", "add", "LICENSE"], cwd=git_dir) + subprocess.check_output(["git", "commit", "-m", "'add license'"], cwd=git_dir) + download_location, relpath = bb.fetch2.trace.get_dl_loc(git_dir) + self.assertEqual((download_location, relpath), (None, None)) + + def test_get_dl_loc_on_file_with_no_git_repo(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + create_file(tmpdir/"README", "hello") + download_location, relpath = bb.fetch2.trace.get_dl_loc(tmpdir) + self.assertEqual((download_location, relpath), (None, None)) + + +class IsInCurrentBranchTest(unittest.TestCase): + + def get_untracked_new_and_modified_files(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + create_file(tmpdir / "repo/README", "hello") + create_file(tmpdir / "repo/doc/help.txt", "help") + git_dir = tmpdir/"repo" + subprocess.check_output(["git", "init"], cwd=git_dir) + subprocess.check_output(["git", "add", "-A"], cwd=git_dir) + subprocess.check_output(["git", "commit", "-m", "'initial commit'"], cwd=git_dir) + + # modified + create_file(tmpdir / "repo/README", "hello there") + # untracked + create_file(tmpdir / "repo/test/test.txt", "test") + # staged, uncommitted + create_file(tmpdir / "repo/test/test2.txt", "test2") + subprocess.check_output(["git", "add", "test/test2.txt"], cwd=git_dir) + + untracked_new_and_modified_files = bb.fetch2.trace.get_get_untracked_new_and_modified_files(git_dir) + + self.assertFalse("doc/help.txt" in untracked_new_and_modified_files) + self.assertTrue("README" in untracked_new_and_modified_files) + self.assertTrue("test/test.txt" in untracked_new_and_modified_files) + self.assertTrue("test/test2.txt" in untracked_new_and_modified_files) + + +class TraceUnpackIntegrationTest(unittest.TestCase): + + meta_repos = [( + "git://git.yoctoproject.org/poky", + "langdale", + "yocto-4.1.3", + "2023-03-05" + ),( + "git://git.openembedded.org/meta-openembedded", + "langdale", + "b5b732876da1885ecbab2aa45f80d7a3086c5262", + "" + )] + + @classmethod + @skipIfNoNetwork() + def setUpClass(cls): + cls.meta_tempdir = tempfile.mkdtemp(prefix="meta-") + for repo, branch, commit, shallow_since in cls.meta_repos: + cmd = "git clone" + if shallow_since: + cmd += " --shallow-since %s" % shallow_since + cmd += " --branch %s --single-branch %s" % (branch, repo) + bb.process.run(cmd, cwd=cls.meta_tempdir) + basename = re.sub(r"\.git$", "", os.path.basename(repo)) + git_dir = os.path.join(cls.meta_tempdir, basename) + bb.process.run("git checkout %s" % commit, cwd=git_dir) + cls.tempdir = tempfile.mkdtemp(prefix="bitbake-trace-") + cls.dldir = os.path.join(cls.tempdir, "download") + os.mkdir(cls.dldir) + + @classmethod + @skipIfNoNetwork() + def tearDownClass(cls): + if os.environ.get("BB_TMPDIR_NOCLEAN") == "yes": + print("Not cleaning up %s. Please remove manually." % cls.meta_tempdir) + print("Not cleaning up %s. Please remove manually." % cls.tempdir) + else: + bb.process.run('chmod u+rw -R %s' % cls.meta_tempdir) + bb.utils.prunedir(cls.meta_tempdir) + bb.process.run('chmod u+rw -R %s' % cls.tempdir) + bb.utils.prunedir(cls.tempdir) + + def run_do_unpack(self, var, var_flags, is_go=False): + self.d = bb.data.init() + self.d.setVar("DL_DIR", self.dldir) + for var_name, value in var.items(): + self.d.setVar(var_name, value) + for var_name, flags in var_flags.items(): + for flag_name, flag_value in flags.items(): + self.d.setVarFlag(var_name, flag_name, flag_value) + bb.utils.mkdirhier(self.d.getVar("S")) + bb.utils.mkdirhier(self.d.getVar("WORKDIR") + "/temp") + fetcher = bb.fetch2.Fetch(None, self.d) + fetcher.download() + if is_go: # simulate go_do_unpack + for url in fetcher.urls: + if fetcher.ud[url].type == 'git': + if fetcher.ud[url].parm.get('destsuffix') is None: + s_dirname = os.path.basename(self.d.getVar('S')) + fetcher.ud[url].parm['destsuffix'] = os.path.join( + s_dirname, 'src', self.d.getVar('GO_IMPORT')) + '/' + fetcher.unpack(self.d.getVar("WORKDIR")) + + def get_trace_data_and_expected_trace_data(self): + json_file = "%s-%s.unpack.trace.json.zst" % (self.d.getVar("PN"), self.d.getVar("PV")) + path = os.path.join(self.d.getVar("WORKDIR"), "temp", json_file) + with bb.compress.zstd.open(path, "rt", encoding="utf-8", num_threads=1) as f: + td = json.load(f) + this_dir = os.path.dirname(os.path.abspath(__file__)) + testdata_path = os.path.join(this_dir, "trace-testdata", json_file) + with bb.compress.zstd.open(testdata_path, "rt", encoding="utf-8", num_threads=1) as f: + expected_td = json.load(f) + return td, expected_td + + @skipIfNoNetwork() + def test_bzip2_case(self): + """ 1) check if https, git and file src uris are correctly traced + 2) local files configure.ac and Makefile.am from poky/meta layer are + added to bzip2 source dir (${WORKDIR}/bzip2-1.0.8/) through + file:// src uris with subdir param: check if their real upstream + source is correctly identified + 3) SRC_URI contains variables to be expanded: check if the + unexpanded src uris are correctly identified + """ + var = { + "PN": "bzip2", + "BPN": "bzip2", + "PV": "1.0.8", + "BP": "${BPN}-${PV}", + "SRC_URI": """https://sourceware.org/pub/${BPN}/${BPN}-${PV}.tar.gz + git://sourceware.org/git/bzip2-tests.git;name=bzip2-tests;branch=master + file://configure.ac;subdir=${BP} + file://Makefile.am;subdir=${BP} + file://run-ptest + """, + "SRCREV_bzip2-tests": "f9061c030a25de5b6829e1abf373057309c734c0", + "FILE": self.meta_tempdir+"/poky/meta/recipes-extended/bzip2/bzip2_1.0.8.bb", + "FILE_DIRNAME": "${@os.path.dirname(d.getVar('FILE', False))}", + "FILESPATH": '${FILE_DIRNAME}/${BP}:${FILE_DIRNAME}/${BPN}:${FILE_DIRNAME}/files', + "WORKDIR": self.tempdir+"/work/core2-64-poky-linux/${PN}/${PV}-r0", + "S": "${WORKDIR}/${BP}", + "BBLAYERS": self.meta_tempdir+"/poky/meta", + } + var_flags = { + "SRC_URI": { + "md5sum": "67e051268d0c475ea773822f7500d0e5", + "sha256sum": "ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269" + } + } + self.run_do_unpack(var, var_flags) + td, expected_td = self.get_trace_data_and_expected_trace_data() + self.assertEqual(td, expected_td) + + @skipIfNoNetwork() + def test_gettext_minimal_native_case(self): + """ check if file src uri pointing to a directory (aclocal/) is + correctly handled""" + var = { + "PN": "gettext-minimal-native", + "PV": "0.21", + "BPN": "gettext-minimal", + "BP": "${BPN}-${PV}", + "SRC_URI": """file://aclocal/ + file://config.rpath + file://Makefile.in.in + file://remove-potcdate.sin + file://COPYING + """, + "FILE": self.meta_tempdir+"/poky/meta/recipes-core/gettext/gettext-minimal-native_0.21.1.bb", + "FILE_DIRNAME": "${@os.path.dirname(d.getVar('FILE', False))}", + "FILESPATH": '${FILE_DIRNAME}/${BP}:${FILE_DIRNAME}/${BPN}:${FILE_DIRNAME}/files', + "WORKDIR": self.tempdir+"/work/x86_64-linux/${PN}/${PV}-r0", + "S": "${WORKDIR}", + "BBLAYERS": self.meta_tempdir+"/poky/meta", + } + var_flags = {} + self.run_do_unpack(var, var_flags) + td, expected_td = self.get_trace_data_and_expected_trace_data() + self.assertEqual(td, expected_td) + + @skipIfNoNetwork() + def test_python_cryptography_case(self): + """ 1) check if crate:// src_uris are handled correctly (download + location should be the corresponding https download url) + 2) check if package checksum data is handled correctly (we have + multiple SRC_URI entries supporting checksums here, but the + checksum var flag set in the recipe refers only to the first + found entry) + """ + var = { + "PN": "python3-cryptography", + "PV": "37.0.4", + "BPN": "python3-cryptography", + "BP": "${BPN}-${PV}", + "PYPI_SRC_URI": "https://files.pythonhosted.org/packages/source/c/cryptography/cryptography-37.0.4.tar.gz", + "SRC_URI": """ + ${PYPI_SRC_URI} + file://run-ptest + file://check-memfree.py + file://0001-Cargo.toml-specify-pem-version.patch + file://0002-Cargo.toml-edition-2018-2021.patch + file://0001-pyproject.toml-remove-benchmark-disable-option.patch + crate://crates.io/Inflector/0.11.4 + crate://crates.io/aliasable/0.1.3 + crate://crates.io/asn1/0.8.7 + crate://crates.io/asn1_derive/0.8.7 + crate://crates.io/autocfg/1.1.0 + crate://crates.io/base64/0.13.0 + crate://crates.io/bitflags/1.3.2 + crate://crates.io/cfg-if/1.0.0 + crate://crates.io/chrono/0.4.19 + crate://crates.io/indoc-impl/0.3.6 + crate://crates.io/indoc/0.3.6 + crate://crates.io/instant/0.1.12 + crate://crates.io/lazy_static/1.4.0 + crate://crates.io/libc/0.2.124 + crate://crates.io/lock_api/0.4.7 + crate://crates.io/num-integer/0.1.44 + crate://crates.io/num-traits/0.2.14 + crate://crates.io/once_cell/1.10.0 + crate://crates.io/ouroboros/0.15.0 + crate://crates.io/ouroboros_macro/0.15.0 + crate://crates.io/parking_lot/0.11.2 + crate://crates.io/parking_lot_core/0.8.5 + crate://crates.io/paste-impl/0.1.18 + crate://crates.io/paste/0.1.18 + crate://crates.io/pem/1.0.2 + crate://crates.io/proc-macro-error-attr/1.0.4 + crate://crates.io/proc-macro-error/1.0.4 + crate://crates.io/proc-macro-hack/0.5.19 + crate://crates.io/proc-macro2/1.0.37 + crate://crates.io/pyo3-build-config/0.15.2 + crate://crates.io/pyo3-macros-backend/0.15.2 + crate://crates.io/pyo3-macros/0.15.2 + crate://crates.io/pyo3/0.15.2 + crate://crates.io/quote/1.0.18 + crate://crates.io/redox_syscall/0.2.13 + crate://crates.io/scopeguard/1.1.0 + crate://crates.io/smallvec/1.8.0 + crate://crates.io/stable_deref_trait/1.2.0 + crate://crates.io/syn/1.0.91 + crate://crates.io/unicode-xid/0.2.2 + crate://crates.io/unindent/0.1.8 + crate://crates.io/version_check/0.9.4 + crate://crates.io/winapi-i686-pc-windows-gnu/0.4.0 + crate://crates.io/winapi-x86_64-pc-windows-gnu/0.4.0 + crate://crates.io/winapi/0.3.9 + """, + "FILE": self.meta_tempdir+"/poky/meta/recipes-devtools/python/python3-cryptography_37.0.4.bb", + "FILE_DIRNAME": "${@os.path.dirname(d.getVar('FILE', False))}", + "FILESPATH": '${FILE_DIRNAME}/${BP}:${FILE_DIRNAME}/${BPN}:${FILE_DIRNAME}/files', + "WORKDIR": self.tempdir+"/work/core2-64-poky-linux/${PN}/${PV}-r0", + "S": "${WORKDIR}/${BP}", + "BBLAYERS": self.meta_tempdir+"/poky/meta", + } + var_flags = { + "SRC_URI": { + "sha256sum": "63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", + } + } + self.run_do_unpack(var, var_flags) + td, expected_td = self.get_trace_data_and_expected_trace_data() + self.assertEqual(td, expected_td) + + @skipIfNoNetwork() + def test_snappy_case(self): + """check if gitsm src uri is handled correctly""" + var = { + "PN": "snappy", + "PV": "1.1.9", + "BPN": "snappy", + "BP": "${BPN}-${PV}", + "SRC_URI": """ + gitsm://github.com/google/snappy.git;protocol=https;branch=main + file://0001-Add-inline-with-SNAPPY_ATTRIBUTE_ALWAYS_INLINE.patch + """, + "SRCREV": "2b63814b15a2aaae54b7943f0cd935892fae628f", + "FILE": self.meta_tempdir+"/meta-openembedded/meta-oe/recipes-extended/snappy/snappy_1.1.9.bb", + "FILE_DIRNAME": "${@os.path.dirname(d.getVar('FILE', False))}", + "FILESPATH": '${FILE_DIRNAME}/${BP}:${FILE_DIRNAME}/${BPN}:${FILE_DIRNAME}/files', + "WORKDIR": self.tempdir+"/work/core2-64-poky-linux/${PN}/${PV}-r0", + "S": "${WORKDIR}/git", + "BBLAYERS": self.meta_tempdir+"/meta-openembedded/meta-oe", + } + var_flags = {} + self.run_do_unpack(var, var_flags) + td, expected_td = self.get_trace_data_and_expected_trace_data() + self.assertEqual(td, expected_td) + + @skipIfNoNetwork() + def test_ot_br_posix_case(self): + """check if nested git submodules are handled correctly""" + var = { + "PN": "ot-br-posix", + "PV": "0.3.0+git083fb6921903441bf44f46c263c123eb4af6e4a9", + "BPN": "ot-br-posix", + "BP": "${BPN}-${PV}", + "SRC_URI": "gitsm://github.com/openthread/ot-br-posix.git;protocol=https;branch=main", + "SRCREV": "083fb6921903441bf44f46c263c123eb4af6e4a9", + "WORKDIR": self.tempdir+"/work/core2-64-poky-linux/${PN}/${PV}-r0", + "S": "${WORKDIR}/git", + "BBLAYERS": self.meta_tempdir+"/meta-openembedded/meta-networking", + } + var_flags = {} + self.run_do_unpack(var, var_flags) + td, expected_td = self.get_trace_data_and_expected_trace_data() + self.assertEqual(td, expected_td) + + @skipIfNoNetwork() + def test_gosu_case(self): + """ 1) test if src uris pointing to go code are handled correctly + (mocking go_do_unpack) + 2) test if SRC_URI entries with local absolute path destsuffix param + are handled correctly + 3) test if symlinks in sources are handled correctly + """ + var = { + "PN": "gosu", + "PV": "1.14", + "BPN": "gosu", + "BP": "${BPN}-${PV}", + "FILE": self.meta_tempdir+"/meta-openembedded/meta-oe/recipes-support/gosu/gosu_1.14.bb", + "WORKDIR": self.tempdir+"/work/core2-64-poky-linux/${PN}/${PV}-r0", + "S": "${WORKDIR}/${BP}", + "GO_IMPORT": "github.com/tianon/gosu", + "SRC_URI": """ + git://${GO_IMPORT}.git;branch=master;protocol=https + git://github.com/opencontainers/runc;name=runc;destsuffix=${S}/src/github.com/opencontainers/runc;branch=main;protocol=https + """, + "SRCREV": "9f7cd138a1ebc0684d43ef6046bf723978e8741f", + "SRCREV_runc": "d7f7b22a85a2387557bdcda125710c2506f8d5c5", + "BBLAYERS": self.meta_tempdir+"/meta-openembedded/meta-oe", + } + var_flags = {} + self.run_do_unpack(var, var_flags, is_go=True) + td, expected_td = self.get_trace_data_and_expected_trace_data() + self.assertEqual(td, expected_td) + + @skipIfNoNetwork() + def test_systemd_case(self): + """check if SRC_URI containing expressions are handled correctly""" + var = { + "PN": "systemd", + "PV": "251.8", + "BPN": "systemd", + "BP": "${BPN}-${PV}", + "SRCBRANCH": "v251-stable", + "SRCREV": "ae8b249af4acb055f920134f2ac584c4cbc86e3b", + "SRC_URI": """ + git://github.com/systemd/systemd-stable.git;protocol=https;branch=${SRCBRANCH} + file://touchscreen.rules + file://00-create-volatile.conf + ${@bb.utils.contains('PACKAGECONFIG', 'polkit_hostnamed_fallback', 'file://org.freedesktop.hostname1_no_polkit.conf', '', d)} + ${@bb.utils.contains('PACKAGECONFIG', 'polkit_hostnamed_fallback', 'file://00-hostnamed-network-user.conf', '', d)} + file://init + file://99-default.preset + file://systemd-pager.sh + file://0001-binfmt-Don-t-install-dependency-links-at-install-tim.patch + file://0003-implment-systemd-sysv-install-for-OE.patch + file://0001-Move-sysusers.d-sysctl.d-binfmt.d-modules-load.d-to-.patch + """, + "FILE": self.meta_tempdir+"/poky/meta/recipes-core/systemd/systemd_251.8.bb", + "FILE_DIRNAME": "${@os.path.dirname(d.getVar('FILE', False))}", + "FILESPATH": '${FILE_DIRNAME}/${BP}:${FILE_DIRNAME}/${BPN}:${FILE_DIRNAME}/files', + "WORKDIR": self.tempdir+"/work/core2-64-poky-linux/${PN}/${PV}-r0", + "S": "${WORKDIR}/git", + "BBLAYERS": self.meta_tempdir+"/poky/meta", + } + var_flags = {} + self.run_do_unpack(var, var_flags) + td, expected_td = self.get_trace_data_and_expected_trace_data() + self.assertEqual(td, expected_td) + + @skipIfNoNetwork() + def test_npmsw(self): + """ 1) test npmsw fetcher using a small made-up npm-shrinkwrap.json + file; check if nested non-dedup dependencies are handled + correctly (some upstream files are replicated in multiple + paths in workdir); check if "git" package versions are + handled correctly + 2) test if files added to existing layer local repos are handled + correctly (finding local provenance and not upstream provenance) + """ + this_dir = os.path.dirname(os.path.abspath(__file__)) + npmsw_file = this_dir+"/trace-testdata/npm-shrinkwrap-test.json" + shutil.copy2(npmsw_file, self.meta_tempdir+"/poky/meta") + var = { + "PN": "test_npm", + "PV": "1.0.0", + "BPN": "test_npm", + "BP": "${BPN}-${PV}", + "NPMSW_PATH": self.meta_tempdir+"/poky/meta", + "SRC_URI": "npmsw://${NPMSW_PATH}/npm-shrinkwrap-test.json", + "WORKDIR": self.tempdir+"/work/all-poky-linux/${PN}/${PV}-r0", + "S": "${WORKDIR}/${BP}", + "BBLAYERS": self.meta_tempdir+"/poky/meta", + } + var_flags = {} + self.run_do_unpack(var, var_flags) + td, expected_td = self.get_trace_data_and_expected_trace_data() + self.assertEqual(td, expected_td) + + +if __name__ == '__main__': + unittest.main()
\ No newline at end of file |