about summary refs log tree commit diff
path: root/nixpkgs/pkgs/development/interpreters/python/hooks/python-catch-conflicts-hook-tests.nix
blob: f3d9235799e01a89db9d7d36b846acbd3fab088c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
{ pythonOnBuildForHost, runCommand, writeShellScript, coreutils, gnugrep }: let

  pythonPkgs = pythonOnBuildForHost.pkgs;

  ### UTILITIES

  # customize a package so that its store paths differs
  customize = pkg: pkg.overrideAttrs { some_modification = true; };

  # generates minimal pyproject.toml
  pyprojectToml = pname: builtins.toFile "pyproject.toml" ''
    [project]
    name = "${pname}"
    version = "1.0.0"
  '';

  # generates source for a python project
  projectSource = pname: runCommand "my-project-source" {} ''
    mkdir -p $out/src
    cp ${pyprojectToml pname} $out/pyproject.toml
    touch $out/src/__init__.py
  '';

  # helper to reduce boilerplate
  generatePythonPackage = args: pythonPkgs.buildPythonPackage (
    {
      version = "1.0.0";
      src = runCommand "my-project-source" {} ''
        mkdir -p $out/src
        cp ${pyprojectToml args.pname} $out/pyproject.toml
        touch $out/src/__init__.py
      '';
      pyproject = true;
      catchConflicts = true;
      buildInputs = [ pythonPkgs.setuptools ];
    }
    // args
  );

  # in order to test for a failing build, wrap it in a shell script
  expectFailure = build: errorMsg: build.overrideDerivation (old: {
    builder = writeShellScript "test-for-failure" ''
      export PATH=${coreutils}/bin:${gnugrep}/bin:$PATH
      ${old.builder} "$@" > ./log 2>&1
      status=$?
      cat ./log
      if [ $status -eq 0 ] || ! grep -q "${errorMsg}" ./log; then
        echo "The build should have failed with '${errorMsg}', but it didn't"
        exit 1
      else
        echo "The build failed as expected with: ${errorMsg}"
        mkdir -p $out
      fi
    '';
  });
in {

  ### TEST CASES

  # Test case which must not trigger any conflicts.
  # This derivation has runtime dependencies on custom versions of multiple build tools.
  # This scenario is relevant for lang2nix tools which do not override the nixpkgs fix-point.
  # see https://github.com/NixOS/nixpkgs/issues/283695
  ignores-build-time-deps =
    generatePythonPackage {
      pname = "ignores-build-time-deps";
      buildInputs = [
        pythonPkgs.build
        pythonPkgs.packaging
        pythonPkgs.setuptools
        pythonPkgs.wheel
      ];
      propagatedBuildInputs = [
        # Add customized versions of build tools as runtime deps
        (customize pythonPkgs.packaging)
        (customize pythonPkgs.setuptools)
        (customize pythonPkgs.wheel)
      ];
    };

  # Simplest test case that should trigger a conflict
  catches-simple-conflict = let
    # this build must fail due to conflicts
    package = pythonPkgs.buildPythonPackage rec {
      pname = "catches-simple-conflict";
      version = "0.0.0";
      src = projectSource pname;
      pyproject = true;
      catchConflicts = true;
      buildInputs = [
        pythonPkgs.setuptools
      ];
      # depend on two different versions of packaging
      # (an actual runtime dependency conflict)
      propagatedBuildInputs = [
        pythonPkgs.packaging
        (customize pythonPkgs.packaging)
      ];
    };
  in
    expectFailure package "Found duplicated packages in closure for dependency 'packaging'";


  /*
    More complex test case with a transitive conflict

    Test sets up this dependency tree:

      toplevel
      ├── dep1
      │   └── leaf
      └── dep2
          └── leaf (customized version -> conflicting)
  */
  catches-transitive-conflict = let
    # package depending on both dependency1 and dependency2
    toplevel = generatePythonPackage {
      pname = "catches-transitive-conflict";
      propagatedBuildInputs = [ dep1 dep2 ];
    };
    # dep1 package depending on leaf
    dep1 = generatePythonPackage {
      pname = "dependency1";
      propagatedBuildInputs = [ leaf ];
    };
    # dep2 package depending on conflicting version of leaf
    dep2 = generatePythonPackage {
      pname = "dependency2";
      propagatedBuildInputs = [ (customize leaf) ];
    };
    # some leaf package
    leaf = generatePythonPackage {
      pname = "leaf";
    };
  in
    expectFailure toplevel "Found duplicated packages in closure for dependency 'leaf'";
}