diff --git a/.circleci/config.yml b/.circleci/config.yml index 14fde59987757ba88639fe78fc5b9852b750b52f..4cb017e96e66ed558b786ab5375e725c3bbe3f4e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -134,12 +134,15 @@ jobs: environment: ZKAPAUTHORIZER_HYPOTHESIS_PROFILE: "ci" - linux-tests: + linux-tests: &LINUX_TESTS docker: # Run in a highly Nix-capable environment. - - image: "nixorg/nix:circleci" + - image: "nixos/nix:latest" environment: + # CACHIX_AUTH_TOKEN is manually set in the CircleCI web UI and allows us to push to CACHIX_NAME. + CACHIX_NAME: "privatestorage-opensource" + # Specify a revision of PrivateStorageio/nixpkgs to run against. This # essentially pins the majority of the software involved in the build. # This revision is selected arbitrarily (it's just new enough to define @@ -147,75 +150,18 @@ jobs: # somewhat current as of the time of this comment. We can bump it to a # newer version when that makes sense. Meanwhile, the platform won't # shift around beneath us unexpectedly. - NIXPKGS_REV: "730129887a84a8f84f3b78ffac7add72aeb551b6" + NIX_PATH: "nixpkgs=https://github.com/PrivateStorageio/nixpkgs/archive/730129887a84a8f84f3b78ffac7add72aeb551b6.tar.gz" steps: - run: - # Get NIX_PATH set for the rest of the job so that the revision of - # nixpkgs we selected will be used everywhere Nix pulls in software. - # There is no way to set an environment variable containing the - # value of another environment variable on CircleCI except to use - # the `BASE_ENV` feature as we do here. - name: "Setup NIX_PATH Environment Variable" + name: "Set up Cachix" command: | - echo "export NIX_PATH=nixpkgs=https://github.com/PrivateStorageio/nixpkgs/archive/$NIXPKGS_REV.tar.gz" >> $BASH_ENV + nix-env -iA nixpkgs.cachix nixpkgs.bash + cachix use "${CACHIX_NAME}" + nix path-info --all > /tmp/store-path-pre-build - "checkout" - - "run": - # CircleCI won't let us interpolate NIXPKGS_REV into a cache key. - # Only CircleCI's own environment variables or variables set via the - # web interface in a "context" can be interpolated into cache keys. - # However, we can interpolate the checksum of a file... Since we - # don't care about the exact revision, we just care that a new - # revision gives us a new string, we can write the revision to a - # file and then put the checksum of that file into the cache key. - # This way, we don't have to maintain the nixpkgs revision in two - # places and risk having them desynchronize. - name: "Prepare For Cache Key" - command: | - echo "${NIXPKGS_REV}" > nixpkgs.rev - - - restore_cache: - # Get all of Nix's state relating to the particular revision of - # nixpkgs we're using. It will always be the same. CircleCI - # artifacts and nixpkgs store objects are probably mostly hosted in - # the same place (S3) so there's not a lot of difference for - # anything that's pre-built. For anything we end up building - # ourselves, though, this saves us all of the build time (less the - # download time). - # - # Read about caching dependencies: https://circleci.com/docs/2.0/caching/ - name: "Restore Nix Store Paths" - keys: - # Construct cache keys that allow sharing as long as nixpkgs - # revision is unchanged. - # - # If nixpkgs changes then potentially a lot of cached packages for - # the base system will be invalidated so we may as well drop them - # and make a new cache with the new packages. - - zkapauthorizer-nix-store-v4-{{ checksum "nixpkgs.rev" }}-ourdeps - - zkapauthorizer-nix-store-v4-{{ checksum "nixpkgs.rev" }}- - - zkapauthorizer-nix-store-v4- - - - run: - name: "Build challenge-bypass-ristretto" - command: | - # Pre-build this because doing so is somewhat memory intensive and - # we want to turn off concurrency for this part. We want to be - # able to leave concurrency on for the rest of the build, though, - # where it doesn't cause problems and speeds things up. - nix-build --cores 1 --max-jobs 1 \ - --arg callPackage '(import <nixpkgs> { }).callPackage' \ - ./python-challenge-bypass-ristretto.nix - - - save_cache: - name: "Cache Nix Store Paths" - when: "always" - key: zkapauthorizer-nix-store-v4-{{ checksum "nixpkgs.rev" }} - paths: - - "/nix" - - run: name: "Run Test Suite" command: | @@ -234,12 +180,24 @@ jobs: --arg collectCoverage true \ --attr doc - - save_cache: - name: "Cache Nix Store Paths" + - run: + name: "Push to Cachix" when: "always" - key: zkapauthorizer-nix-store-v4-{{ checksum "nixpkgs.rev" }}-ourdeps - paths: - - "/nix" + command: | + # Cribbed from + # https://circleci.com/blog/managing-secrets-when-you-have-pull-requests-from-outside-contributors/ + if [ -n "$CIRCLE_PR_NUMBER" ]; then + # I'm sure you're thinking "CIRCLE_PR_NUMBER must just be the + # number of the PR being built". Sorry, dear reader, you have + # guessed poorly. It is also conditionally set based on whether + # this is a PR from a fork or not. + # + # https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables + echo "Skipping Cachix push for forked PR." + else + # https://docs.cachix.org/continuous-integration-setup/circleci.html + bash -c "comm -13 <(sort /tmp/store-path-pre-build | grep -v '\.drv$') <(nix path-info --all | grep -v '\.drv$' | sort) | cachix push $CACHIX_NAME" + fi - run: name: "Report Coverage" diff --git a/src/_zkapauthorizer/storage_common.py b/src/_zkapauthorizer/storage_common.py index dd9a9f49e15bd542aa70cbbbdaadedfde5c64bbb..487c164aa7f5cce69a2e9a66d5cbbfaa475ecd4e 100644 --- a/src/_zkapauthorizer/storage_common.py +++ b/src/_zkapauthorizer/storage_common.py @@ -38,7 +38,7 @@ from pyutil.mathutil import ( div_ceil, ) -@attr.s(frozen=True) +@attr.s(frozen=True, str=True) class MorePassesRequired(Exception): """ Storage operations fail with ``MorePassesRequired`` when they are not @@ -50,11 +50,11 @@ class MorePassesRequired(Exception): ivar int required_count: The number of valid passes which must be presented for the operation to be authorized. - :ivar list[int] signature_check_failed: Indices into the supplied list of + :ivar set[int] signature_check_failed: Indices into the supplied list of passes indicating passes which failed the signature check. """ - valid_count = attr.ib() - required_count = attr.ib() + valid_count = attr.ib(validator=attr.validators.instance_of((int, long))) + required_count = attr.ib(validator=attr.validators.instance_of((int, long))) signature_check_failed = attr.ib(converter=frozenset) diff --git a/src/_zkapauthorizer/tests/test_storage_server.py b/src/_zkapauthorizer/tests/test_storage_server.py index 1eddf1c2e2c173eda5ad4209c5a5397cf146dccb..314a0cf6ff21465769fcf3b73a8f4d456566e1c3 100644 --- a/src/_zkapauthorizer/tests/test_storage_server.py +++ b/src/_zkapauthorizer/tests/test_storage_server.py @@ -33,6 +33,8 @@ from testtools import ( ) from testtools.matchers import ( Equals, + AfterPreprocessing, + MatchesAll, ) from hypothesis import ( given, @@ -157,6 +159,36 @@ class ValidationResultTests(TestCase): ), ) + def test_raise_for(self): + """ + ``_ValidationResult.raise_for`` raises ``MorePassesRequired`` populated + with details of the validation and how it fell short of what was + required. + """ + good = [0, 1, 2, 3] + badsig = [4] + required = 10 + result = _ValidationResult(good, badsig) + try: + result.raise_for(required) + except MorePassesRequired as exc: + self.assertThat( + exc, + MatchesAll( + Equals( + MorePassesRequired( + len(good), + required, + set(badsig), + ), + ), + AfterPreprocessing( + str, + Equals("MorePassesRequired(valid_count=4, required_count=10, signature_check_failed=frozenset([4]))"), + ), + ), + ) + class PassValidationTests(TestCase): """