# Tests for the Python interpreters, package sets and environments.
#
# Each Python interpreter has a `passthru.tests` which is the attribute set
# returned by this function. For example, for Python 3 the tests are run with
#
# $ nix-build -A python3.tests
#
{ stdenv
, python
, runCommand
, lib
, callPackage
, pkgs
}:

let
  # Test whether the interpreter behaves in the different types of environments
  # we aim to support.
  environmentTests = let
    envs = let
      inherit python;
      pythonEnv = python.withPackages(ps: with ps; [ ]);
      pythonVirtualEnv = if python.isPy3k
        then
           python.withPackages(ps: with ps; [ virtualenv ])
        else
          python.buildEnv.override {
            extraLibs = with python.pkgs; [ virtualenv ];
            # Collisions because of namespaces __init__.py
            ignoreCollisions = true;
          };
    in {
      # Plain Python interpreter
      plain = rec {
        env = python;
        interpreter = env.interpreter;
        is_venv = "False";
        is_nixenv = "False";
        is_virtualenv = "False";
      };
    } // lib.optionalAttrs (!python.isPyPy && !stdenv.isDarwin) {
      # Use virtualenv from a Nix env.
      # Fails on darwin with
      #   virtualenv: error: argument dest: the destination . is not write-able at /nix/store
      nixenv-virtualenv = rec {
        env = runCommand "${python.name}-virtualenv" {} ''
          ${pythonVirtualEnv.interpreter} -m virtualenv venv
          mv venv $out
        '';
        interpreter = "${env}/bin/${python.executable}";
        is_venv = "False";
        is_nixenv = "True";
        is_virtualenv = "True";
      };
    } // lib.optionalAttrs (python.implementation != "graal") {
      # Python Nix environment (python.buildEnv)
      nixenv = rec {
        env = pythonEnv;
        interpreter = env.interpreter;
        is_venv = "False";
        is_nixenv = "True";
        is_virtualenv = "False";
      };
    } // lib.optionalAttrs (python.isPy3k && (!python.isPyPy)) {
      # Venv built using plain Python
      # Python 2 does not support venv
      # TODO: PyPy executable name is incorrect, it should be pypy-c or pypy-3c instead of pypy and pypy3.
      plain-venv = rec {
        env = runCommand "${python.name}-venv" {} ''
          ${python.interpreter} -m venv $out
        '';
        interpreter = "${env}/bin/${python.executable}";
        is_venv = "True";
        is_nixenv = "False";
        is_virtualenv = "False";
      };

    } // lib.optionalAttrs (python.pythonAtLeast "3.8") {
      # Venv built using Python Nix environment (python.buildEnv)
      # TODO: Cannot create venv from a  nix env
      # Error: Command '['/nix/store/ddc8nqx73pda86ibvhzdmvdsqmwnbjf7-python3-3.7.6-venv/bin/python3.7', '-Im', 'ensurepip', '--upgrade', '--default-pip']' returned non-zero exit status 1.
      nixenv-venv = rec {
        env = runCommand "${python.name}-venv" {} ''
          ${pythonEnv.interpreter} -m venv $out
        '';
        interpreter = "${env}/bin/${pythonEnv.executable}";
        is_venv = "True";
        is_nixenv = "True";
        is_virtualenv = "False";
      };
    };

    testfun = name: attrs: runCommand "${python.name}-tests-${name}" ({
      inherit (python) pythonVersion;
    } // attrs) ''
      cp -r ${./tests/test_environments} tests
      chmod -R +w tests
      substituteAllInPlace tests/test_python.py
      ${attrs.interpreter} -m unittest discover --verbose tests #/test_python.py
      mkdir $out
      touch $out/success
    '';

  in lib.mapAttrs testfun envs;

  # Integration tests involving the package set.
  # All PyPy package builds are broken at the moment
  integrationTests = lib.optionalAttrs (!python.isPyPy) (
    lib.optionalAttrs (python.isPy3k && !stdenv.isDarwin) { # darwin has no split-debug
      cpython-gdb = callPackage ./tests/test_cpython_gdb {
        interpreter = python;
      };
    } // lib.optionalAttrs (python.pythonAtLeast "3.7") {
      # Before the addition of NIX_PYTHONPREFIX mypy was broken with typed packages
      nix-pythonprefix-mypy = callPackage ./tests/test_nix_pythonprefix {
        interpreter = python;
      };
      # Make sure tkinter is importable. See https://github.com/NixOS/nixpkgs/issues/238990
      tkinter = callPackage ./tests/test_tkinter {
        interpreter = python;
      };
    }
  );

  # Tests to ensure overriding works as expected.
  overrideTests = let
    extension = self: super: {
      foobar = super.numpy;
    };
    # `pythonInterpreters.pypy39_prebuilt` does not expose an attribute
    # name (is not present in top-level `pkgs`).
    is_prebuilt = python: python.pythonAttr == null;
  in lib.optionalAttrs (python.isPy3k) ({
    test-packageOverrides = let
      myPython = let
        self = python.override {
          packageOverrides = extension;
          inherit self;
        };
      in self;
    in assert myPython.pkgs.foobar == myPython.pkgs.numpy; myPython.withPackages(ps: with ps; [ foobar ]);
    # overrideScope is broken currently
    # test-overrideScope = let
    #  myPackages = python.pkgs.overrideScope extension;
    # in assert myPackages.foobar == myPackages.numpy; myPackages.python.withPackages(ps: with ps; [ foobar ]);
    #
    # Have to skip prebuilt python as it's not present in top-level
    # `pkgs` as an attribute.
  } // lib.optionalAttrs (python ? pythonAttr && !is_prebuilt python) {
    # Test applying overrides using pythonPackagesOverlays.
    test-pythonPackagesExtensions = let
      pkgs_ = pkgs.extend(final: prev: {
        pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [
          (python-final: python-prev: {
            foo = python-prev.setuptools;
          })
        ];
      });
    in pkgs_.${python.pythonAttr}.pkgs.foo;
  });

  condaTests = let
    requests = callPackage ({
        autoPatchelfHook,
        fetchurl,
        pythonCondaPackages,
      }:
      python.pkgs.buildPythonPackage {
        pname = "requests";
        version = "2.24.0";
        format = "other";
        src = fetchurl {
          url = "https://repo.anaconda.com/pkgs/main/noarch/requests-2.24.0-py_0.tar.bz2";
          sha256 = "02qzaf6gwsqbcs69pix1fnjxzgnngwzvrsy65h1d521g750mjvvp";
        };
        nativeBuildInputs = [ autoPatchelfHook ] ++ (with python.pkgs; [
          condaUnpackHook condaInstallHook
        ]);
        buildInputs = [
          pythonCondaPackages.condaPatchelfLibs
        ];
        propagatedBuildInputs = with python.pkgs; [
          chardet idna urllib3 certifi
        ];
      }
    ) {};
    pythonWithRequests = requests.pythonModule.withPackages (ps: [ requests ]);
    in lib.optionalAttrs (python.isPy3k && stdenv.isLinux)
    {
      condaExamplePackage = runCommand "import-requests" {} ''
        ${pythonWithRequests.interpreter} -c "import requests" > $out
      '';
    };

in lib.optionalAttrs (stdenv.hostPlatform == stdenv.buildPlatform ) (environmentTests // integrationTests // overrideTests // condaTests)
