├── .clang-format
├── .github
├── demo.gif
├── dependabot.yml
└── workflows
│ └── nix-github-actions.yml
├── .gitignore
├── .mergify.yml
├── .nix-version
├── LICENSE.md
├── README.md
├── default.nix
├── dev
└── treefmt.nix
├── doc
├── .gitignore
├── book.toml
├── default.nix
├── dev
│ ├── highlightjs.nix
│ └── update_highlight.sh
├── src
│ ├── FAQ.md
│ ├── HACKING.md
│ ├── SUMMARY.md
│ ├── coverage.md
│ ├── examples
│ │ ├── errors.md
│ │ ├── flake-parts.md
│ │ ├── flakes.md
│ │ ├── simple.md
│ │ └── trees.md
│ └── introduction.md
└── theme
│ └── highlight.js
├── flake.lock
├── flake.nix
├── lib
├── coverage.nix
├── default.nix
├── flake-checks
│ ├── flake.lock
│ └── flake.nix
├── modules.nix
├── modules
│ ├── flake
│ │ ├── dogfood.nix
│ │ ├── system-agnostic.nix
│ │ ├── system.nix
│ │ └── tests-output.nix
│ └── types.nix
└── test_coverage.nix
├── meson.build
├── renovate.json
├── src
├── meson.build
└── nix-unit.cc
├── templates
├── flake-module.nix
└── flake-parts
│ └── flake.nix
└── tests
├── assets
├── basic.nix
├── flake.lock
└── flake.nix
└── tests.py
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: llvm
2 | IndentWidth: 4
3 | SortIncludes: false
4 |
--------------------------------------------------------------------------------
/.github/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nix-community/nix-unit/be0d299e89a31e246c5472bf0e1005d4cc1e9e55/.github/demo.gif
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 |
--------------------------------------------------------------------------------
/.github/workflows/nix-github-actions.yml:
--------------------------------------------------------------------------------
1 | name: Nix Flake actions
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 | - main
9 |
10 | jobs:
11 | nix-matrix:
12 | runs-on: ubuntu-latest
13 | outputs:
14 | matrix: ${{ steps.set-matrix.outputs.matrix }}
15 | steps:
16 | - uses: actions/checkout@v4
17 | - uses: cachix/install-nix-action@v31
18 | - id: set-matrix
19 | name: Generate Nix Matrix
20 | run: |
21 | set -Eeu
22 | echo "matrix=$(nix eval --json '.#githubActions.matrix')" >> "$GITHUB_OUTPUT"
23 |
24 | nix-build:
25 | needs: nix-matrix
26 | runs-on: ${{ matrix.os }}
27 | strategy:
28 | matrix: ${{fromJSON(needs.nix-matrix.outputs.matrix)}}
29 | steps:
30 | - uses: actions/checkout@v4
31 | - uses: cachix/install-nix-action@v31
32 | - run: nix build -L ".#${{ matrix.attr }}"
33 |
34 | nix-unit:
35 | runs-on: ubuntu-latest
36 | steps:
37 | - uses: actions/checkout@v4
38 | - name: Install Nix
39 | uses: DeterminateSystems/nix-installer-action@v17
40 | - name: Build shell
41 | run: nix develop -c true
42 | - name: Test nix-unit
43 | run: nix develop -c ./tests/tests.py
44 |
45 | collect:
46 | runs-on: ubuntu-latest
47 | needs:
48 | - nix-build
49 | - nix-unit
50 | steps:
51 | - run: exit 0
52 |
53 | deploy-pages:
54 | if: github.ref == 'refs/heads/main'
55 | runs-on: ubuntu-latest
56 | needs: collect
57 | steps:
58 | - uses: actions/checkout@v4
59 | - uses: cachix/install-nix-action@v31
60 | with:
61 | github_access_token: ${{ secrets.GITHUB_TOKEN }}
62 | - name: Run build
63 | run: nix build -L .#doc
64 | - name: Deploy
65 | uses: peaceiris/actions-gh-pages@v4
66 | with:
67 | github_token: ${{ secrets.GITHUB_TOKEN }}
68 | publish_dir: ./result
69 | force_orphan: true
70 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | *.log
4 |
5 | tmp/
6 |
7 |
8 | # Prerequisites
9 | *.d
10 |
11 | # Compiled Object files
12 | *.slo
13 | *.lo
14 | *.o
15 | *.obj
16 |
17 | # Precompiled Headers
18 | *.gch
19 | *.pch
20 |
21 | # Compiled Dynamic libraries
22 | *.so
23 | *.dylib
24 | *.dll
25 |
26 | # Fortran module files
27 | *.mod
28 | *.smod
29 |
30 | # Compiled Static libraries
31 | *.lai
32 | *.la
33 | *.a
34 | *.lib
35 |
36 | # Executables
37 | *.exe
38 | *.out
39 | *.app
40 |
41 | # build directory
42 | /build
43 | # nix-build
44 | /result
45 |
46 | # Byte-compiled / optimized / DLL files
47 | __pycache__/
48 | *.py[cod]
49 | *$py.class
50 |
51 | # mypy
52 | .mypy_cache/
53 | .dmypy.json
54 | dmypy.json
55 |
56 | # nix-direnv
57 | .direnv
58 |
--------------------------------------------------------------------------------
/.mergify.yml:
--------------------------------------------------------------------------------
1 | pull_request_rules:
2 | - name: automatic merge for Renovate pull requests
3 | conditions:
4 | - author=renovate[bot]
5 | - check-success=collect
6 | - check-success=nix-unit
7 | actions:
8 | merge:
9 | method: rebase
10 |
--------------------------------------------------------------------------------
/.nix-version:
--------------------------------------------------------------------------------
1 | unstable
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU General Public License
2 | ==========================
3 |
4 | _Version 3, 29 June 2007_
5 | _Copyright © 2007 Free Software Foundation, Inc. <>_
6 |
7 | Everyone is permitted to copy and distribute verbatim copies of this license
8 | document, but changing it is not allowed.
9 |
10 | ## Preamble
11 |
12 | The GNU General Public License is a free, copyleft license for software and other
13 | kinds of works.
14 |
15 | The licenses for most software and other practical works are designed to take away
16 | your freedom to share and change the works. By contrast, the GNU General Public
17 | License is intended to guarantee your freedom to share and change all versions of a
18 | program--to make sure it remains free software for all its users. We, the Free
19 | Software Foundation, use the GNU General Public License for most of our software; it
20 | applies also to any other work released this way by its authors. You can apply it to
21 | your programs, too.
22 |
23 | When we speak of free software, we are referring to freedom, not price. Our General
24 | Public Licenses are designed to make sure that you have the freedom to distribute
25 | copies of free software (and charge for them if you wish), that you receive source
26 | code or can get it if you want it, that you can change the software or use pieces of
27 | it in new free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you these rights or
30 | asking you to surrender the rights. Therefore, you have certain responsibilities if
31 | you distribute copies of the software, or if you modify it: responsibilities to
32 | respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether gratis or for a fee,
35 | you must pass on to the recipients the same freedoms that you received. You must make
36 | sure that they, too, receive or can get the source code. And you must show them these
37 | terms so they know their rights.
38 |
39 | Developers that use the GNU GPL protect your rights with two steps: **(1)** assert
40 | copyright on the software, and **(2)** offer you this License giving you legal permission
41 | to copy, distribute and/or modify it.
42 |
43 | For the developers' and authors' protection, the GPL clearly explains that there is
44 | no warranty for this free software. For both users' and authors' sake, the GPL
45 | requires that modified versions be marked as changed, so that their problems will not
46 | be attributed erroneously to authors of previous versions.
47 |
48 | Some devices are designed to deny users access to install or run modified versions of
49 | the software inside them, although the manufacturer can do so. This is fundamentally
50 | incompatible with the aim of protecting users' freedom to change the software. The
51 | systematic pattern of such abuse occurs in the area of products for individuals to
52 | use, which is precisely where it is most unacceptable. Therefore, we have designed
53 | this version of the GPL to prohibit the practice for those products. If such problems
54 | arise substantially in other domains, we stand ready to extend this provision to
55 | those domains in future versions of the GPL, as needed to protect the freedom of
56 | users.
57 |
58 | Finally, every program is threatened constantly by software patents. States should
59 | not allow patents to restrict development and use of software on general-purpose
60 | computers, but in those that do, we wish to avoid the special danger that patents
61 | applied to a free program could make it effectively proprietary. To prevent this, the
62 | GPL assures that patents cannot be used to render the program non-free.
63 |
64 | The precise terms and conditions for copying, distribution and modification follow.
65 |
66 | ## TERMS AND CONDITIONS
67 |
68 | ### 0. Definitions
69 |
70 | “This License” refers to version 3 of the GNU General Public License.
71 |
72 | “Copyright” also means copyright-like laws that apply to other kinds of
73 | works, such as semiconductor masks.
74 |
75 | “The Program” refers to any copyrightable work licensed under this
76 | License. Each licensee is addressed as “you”. “Licensees” and
77 | “recipients” may be individuals or organizations.
78 |
79 | To “modify” a work means to copy from or adapt all or part of the work in
80 | a fashion requiring copyright permission, other than the making of an exact copy. The
81 | resulting work is called a “modified version” of the earlier work or a
82 | work “based on” the earlier work.
83 |
84 | A “covered work” means either the unmodified Program or a work based on
85 | the Program.
86 |
87 | To “propagate” a work means to do anything with it that, without
88 | permission, would make you directly or secondarily liable for infringement under
89 | applicable copyright law, except executing it on a computer or modifying a private
90 | copy. Propagation includes copying, distribution (with or without modification),
91 | making available to the public, and in some countries other activities as well.
92 |
93 | To “convey” a work means any kind of propagation that enables other
94 | parties to make or receive copies. Mere interaction with a user through a computer
95 | network, with no transfer of a copy, is not conveying.
96 |
97 | An interactive user interface displays “Appropriate Legal Notices” to the
98 | extent that it includes a convenient and prominently visible feature that **(1)**
99 | displays an appropriate copyright notice, and **(2)** tells the user that there is no
100 | warranty for the work (except to the extent that warranties are provided), that
101 | licensees may convey the work under this License, and how to view a copy of this
102 | License. If the interface presents a list of user commands or options, such as a
103 | menu, a prominent item in the list meets this criterion.
104 |
105 | ### 1. Source Code
106 |
107 | The “source code” for a work means the preferred form of the work for
108 | making modifications to it. “Object code” means any non-source form of a
109 | work.
110 |
111 | A “Standard Interface” means an interface that either is an official
112 | standard defined by a recognized standards body, or, in the case of interfaces
113 | specified for a particular programming language, one that is widely used among
114 | developers working in that language.
115 |
116 | The “System Libraries” of an executable work include anything, other than
117 | the work as a whole, that **(a)** is included in the normal form of packaging a Major
118 | Component, but which is not part of that Major Component, and **(b)** serves only to
119 | enable use of the work with that Major Component, or to implement a Standard
120 | Interface for which an implementation is available to the public in source code form.
121 | A “Major Component”, in this context, means a major essential component
122 | (kernel, window system, and so on) of the specific operating system (if any) on which
123 | the executable work runs, or a compiler used to produce the work, or an object code
124 | interpreter used to run it.
125 |
126 | The “Corresponding Source” for a work in object code form means all the
127 | source code needed to generate, install, and (for an executable work) run the object
128 | code and to modify the work, including scripts to control those activities. However,
129 | it does not include the work's System Libraries, or general-purpose tools or
130 | generally available free programs which are used unmodified in performing those
131 | activities but which are not part of the work. For example, Corresponding Source
132 | includes interface definition files associated with source files for the work, and
133 | the source code for shared libraries and dynamically linked subprograms that the work
134 | is specifically designed to require, such as by intimate data communication or
135 | control flow between those subprograms and other parts of the work.
136 |
137 | The Corresponding Source need not include anything that users can regenerate
138 | automatically from other parts of the Corresponding Source.
139 |
140 | The Corresponding Source for a work in source code form is that same work.
141 |
142 | ### 2. Basic Permissions
143 |
144 | All rights granted under this License are granted for the term of copyright on the
145 | Program, and are irrevocable provided the stated conditions are met. This License
146 | explicitly affirms your unlimited permission to run the unmodified Program. The
147 | output from running a covered work is covered by this License only if the output,
148 | given its content, constitutes a covered work. This License acknowledges your rights
149 | of fair use or other equivalent, as provided by copyright law.
150 |
151 | You may make, run and propagate covered works that you do not convey, without
152 | conditions so long as your license otherwise remains in force. You may convey covered
153 | works to others for the sole purpose of having them make modifications exclusively
154 | for you, or provide you with facilities for running those works, provided that you
155 | comply with the terms of this License in conveying all material for which you do not
156 | control copyright. Those thus making or running the covered works for you must do so
157 | exclusively on your behalf, under your direction and control, on terms that prohibit
158 | them from making any copies of your copyrighted material outside their relationship
159 | with you.
160 |
161 | Conveying under any other circumstances is permitted solely under the conditions
162 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
163 |
164 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
165 |
166 | No covered work shall be deemed part of an effective technological measure under any
167 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty
168 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention
169 | of such measures.
170 |
171 | When you convey a covered work, you waive any legal power to forbid circumvention of
172 | technological measures to the extent such circumvention is effected by exercising
173 | rights under this License with respect to the covered work, and you disclaim any
174 | intention to limit operation or modification of the work as a means of enforcing,
175 | against the work's users, your or third parties' legal rights to forbid circumvention
176 | of technological measures.
177 |
178 | ### 4. Conveying Verbatim Copies
179 |
180 | You may convey verbatim copies of the Program's source code as you receive it, in any
181 | medium, provided that you conspicuously and appropriately publish on each copy an
182 | appropriate copyright notice; keep intact all notices stating that this License and
183 | any non-permissive terms added in accord with section 7 apply to the code; keep
184 | intact all notices of the absence of any warranty; and give all recipients a copy of
185 | this License along with the Program.
186 |
187 | You may charge any price or no price for each copy that you convey, and you may offer
188 | support or warranty protection for a fee.
189 |
190 | ### 5. Conveying Modified Source Versions
191 |
192 | You may convey a work based on the Program, or the modifications to produce it from
193 | the Program, in the form of source code under the terms of section 4, provided that
194 | you also meet all of these conditions:
195 |
196 | * **a)** The work must carry prominent notices stating that you modified it, and giving a
197 | relevant date.
198 | * **b)** The work must carry prominent notices stating that it is released under this
199 | License and any conditions added under section 7. This requirement modifies the
200 | requirement in section 4 to “keep intact all notices”.
201 | * **c)** You must license the entire work, as a whole, under this License to anyone who
202 | comes into possession of a copy. This License will therefore apply, along with any
203 | applicable section 7 additional terms, to the whole of the work, and all its parts,
204 | regardless of how they are packaged. This License gives no permission to license the
205 | work in any other way, but it does not invalidate such permission if you have
206 | separately received it.
207 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal
208 | Notices; however, if the Program has interactive interfaces that do not display
209 | Appropriate Legal Notices, your work need not make them do so.
210 |
211 | A compilation of a covered work with other separate and independent works, which are
212 | not by their nature extensions of the covered work, and which are not combined with
213 | it such as to form a larger program, in or on a volume of a storage or distribution
214 | medium, is called an “aggregate” if the compilation and its resulting
215 | copyright are not used to limit the access or legal rights of the compilation's users
216 | beyond what the individual works permit. Inclusion of a covered work in an aggregate
217 | does not cause this License to apply to the other parts of the aggregate.
218 |
219 | ### 6. Conveying Non-Source Forms
220 |
221 | You may convey a covered work in object code form under the terms of sections 4 and
222 | 5, provided that you also convey the machine-readable Corresponding Source under the
223 | terms of this License, in one of these ways:
224 |
225 | * **a)** Convey the object code in, or embodied in, a physical product (including a
226 | physical distribution medium), accompanied by the Corresponding Source fixed on a
227 | durable physical medium customarily used for software interchange.
228 | * **b)** Convey the object code in, or embodied in, a physical product (including a
229 | physical distribution medium), accompanied by a written offer, valid for at least
230 | three years and valid for as long as you offer spare parts or customer support for
231 | that product model, to give anyone who possesses the object code either **(1)** a copy of
232 | the Corresponding Source for all the software in the product that is covered by this
233 | License, on a durable physical medium customarily used for software interchange, for
234 | a price no more than your reasonable cost of physically performing this conveying of
235 | source, or **(2)** access to copy the Corresponding Source from a network server at no
236 | charge.
237 | * **c)** Convey individual copies of the object code with a copy of the written offer to
238 | provide the Corresponding Source. This alternative is allowed only occasionally and
239 | noncommercially, and only if you received the object code with such an offer, in
240 | accord with subsection 6b.
241 | * **d)** Convey the object code by offering access from a designated place (gratis or for
242 | a charge), and offer equivalent access to the Corresponding Source in the same way
243 | through the same place at no further charge. You need not require recipients to copy
244 | the Corresponding Source along with the object code. If the place to copy the object
245 | code is a network server, the Corresponding Source may be on a different server
246 | (operated by you or a third party) that supports equivalent copying facilities,
247 | provided you maintain clear directions next to the object code saying where to find
248 | the Corresponding Source. Regardless of what server hosts the Corresponding Source,
249 | you remain obligated to ensure that it is available for as long as needed to satisfy
250 | these requirements.
251 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform
252 | other peers where the object code and Corresponding Source of the work are being
253 | offered to the general public at no charge under subsection 6d.
254 |
255 | A separable portion of the object code, whose source code is excluded from the
256 | Corresponding Source as a System Library, need not be included in conveying the
257 | object code work.
258 |
259 | A “User Product” is either **(1)** a “consumer product”, which
260 | means any tangible personal property which is normally used for personal, family, or
261 | household purposes, or **(2)** anything designed or sold for incorporation into a
262 | dwelling. In determining whether a product is a consumer product, doubtful cases
263 | shall be resolved in favor of coverage. For a particular product received by a
264 | particular user, “normally used” refers to a typical or common use of
265 | that class of product, regardless of the status of the particular user or of the way
266 | in which the particular user actually uses, or expects or is expected to use, the
267 | product. A product is a consumer product regardless of whether the product has
268 | substantial commercial, industrial or non-consumer uses, unless such uses represent
269 | the only significant mode of use of the product.
270 |
271 | “Installation Information” for a User Product means any methods,
272 | procedures, authorization keys, or other information required to install and execute
273 | modified versions of a covered work in that User Product from a modified version of
274 | its Corresponding Source. The information must suffice to ensure that the continued
275 | functioning of the modified object code is in no case prevented or interfered with
276 | solely because modification has been made.
277 |
278 | If you convey an object code work under this section in, or with, or specifically for
279 | use in, a User Product, and the conveying occurs as part of a transaction in which
280 | the right of possession and use of the User Product is transferred to the recipient
281 | in perpetuity or for a fixed term (regardless of how the transaction is
282 | characterized), the Corresponding Source conveyed under this section must be
283 | accompanied by the Installation Information. But this requirement does not apply if
284 | neither you nor any third party retains the ability to install modified object code
285 | on the User Product (for example, the work has been installed in ROM).
286 |
287 | The requirement to provide Installation Information does not include a requirement to
288 | continue to provide support service, warranty, or updates for a work that has been
289 | modified or installed by the recipient, or for the User Product in which it has been
290 | modified or installed. Access to a network may be denied when the modification itself
291 | materially and adversely affects the operation of the network or violates the rules
292 | and protocols for communication across the network.
293 |
294 | Corresponding Source conveyed, and Installation Information provided, in accord with
295 | this section must be in a format that is publicly documented (and with an
296 | implementation available to the public in source code form), and must require no
297 | special password or key for unpacking, reading or copying.
298 |
299 | ### 7. Additional Terms
300 |
301 | “Additional permissions” are terms that supplement the terms of this
302 | License by making exceptions from one or more of its conditions. Additional
303 | permissions that are applicable to the entire Program shall be treated as though they
304 | were included in this License, to the extent that they are valid under applicable
305 | law. If additional permissions apply only to part of the Program, that part may be
306 | used separately under those permissions, but the entire Program remains governed by
307 | this License without regard to the additional permissions.
308 |
309 | When you convey a copy of a covered work, you may at your option remove any
310 | additional permissions from that copy, or from any part of it. (Additional
311 | permissions may be written to require their own removal in certain cases when you
312 | modify the work.) You may place additional permissions on material, added by you to a
313 | covered work, for which you have or can give appropriate copyright permission.
314 |
315 | Notwithstanding any other provision of this License, for material you add to a
316 | covered work, you may (if authorized by the copyright holders of that material)
317 | supplement the terms of this License with terms:
318 |
319 | * **a)** Disclaiming warranty or limiting liability differently from the terms of
320 | sections 15 and 16 of this License; or
321 | * **b)** Requiring preservation of specified reasonable legal notices or author
322 | attributions in that material or in the Appropriate Legal Notices displayed by works
323 | containing it; or
324 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that
325 | modified versions of such material be marked in reasonable ways as different from the
326 | original version; or
327 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the
328 | material; or
329 | * **e)** Declining to grant rights under trademark law for use of some trade names,
330 | trademarks, or service marks; or
331 | * **f)** Requiring indemnification of licensors and authors of that material by anyone
332 | who conveys the material (or modified versions of it) with contractual assumptions of
333 | liability to the recipient, for any liability that these contractual assumptions
334 | directly impose on those licensors and authors.
335 |
336 | All other non-permissive additional terms are considered “further
337 | restrictions” within the meaning of section 10. If the Program as you received
338 | it, or any part of it, contains a notice stating that it is governed by this License
339 | along with a term that is a further restriction, you may remove that term. If a
340 | license document contains a further restriction but permits relicensing or conveying
341 | under this License, you may add to a covered work material governed by the terms of
342 | that license document, provided that the further restriction does not survive such
343 | relicensing or conveying.
344 |
345 | If you add terms to a covered work in accord with this section, you must place, in
346 | the relevant source files, a statement of the additional terms that apply to those
347 | files, or a notice indicating where to find the applicable terms.
348 |
349 | Additional terms, permissive or non-permissive, may be stated in the form of a
350 | separately written license, or stated as exceptions; the above requirements apply
351 | either way.
352 |
353 | ### 8. Termination
354 |
355 | You may not propagate or modify a covered work except as expressly provided under
356 | this License. Any attempt otherwise to propagate or modify it is void, and will
357 | automatically terminate your rights under this License (including any patent licenses
358 | granted under the third paragraph of section 11).
359 |
360 | However, if you cease all violation of this License, then your license from a
361 | particular copyright holder is reinstated **(a)** provisionally, unless and until the
362 | copyright holder explicitly and finally terminates your license, and **(b)** permanently,
363 | if the copyright holder fails to notify you of the violation by some reasonable means
364 | prior to 60 days after the cessation.
365 |
366 | Moreover, your license from a particular copyright holder is reinstated permanently
367 | if the copyright holder notifies you of the violation by some reasonable means, this
368 | is the first time you have received notice of violation of this License (for any
369 | work) from that copyright holder, and you cure the violation prior to 30 days after
370 | your receipt of the notice.
371 |
372 | Termination of your rights under this section does not terminate the licenses of
373 | parties who have received copies or rights from you under this License. If your
374 | rights have been terminated and not permanently reinstated, you do not qualify to
375 | receive new licenses for the same material under section 10.
376 |
377 | ### 9. Acceptance Not Required for Having Copies
378 |
379 | You are not required to accept this License in order to receive or run a copy of the
380 | Program. Ancillary propagation of a covered work occurring solely as a consequence of
381 | using peer-to-peer transmission to receive a copy likewise does not require
382 | acceptance. However, nothing other than this License grants you permission to
383 | propagate or modify any covered work. These actions infringe copyright if you do not
384 | accept this License. Therefore, by modifying or propagating a covered work, you
385 | indicate your acceptance of this License to do so.
386 |
387 | ### 10. Automatic Licensing of Downstream Recipients
388 |
389 | Each time you convey a covered work, the recipient automatically receives a license
390 | from the original licensors, to run, modify and propagate that work, subject to this
391 | License. You are not responsible for enforcing compliance by third parties with this
392 | License.
393 |
394 | An “entity transaction” is a transaction transferring control of an
395 | organization, or substantially all assets of one, or subdividing an organization, or
396 | merging organizations. If propagation of a covered work results from an entity
397 | transaction, each party to that transaction who receives a copy of the work also
398 | receives whatever licenses to the work the party's predecessor in interest had or
399 | could give under the previous paragraph, plus a right to possession of the
400 | Corresponding Source of the work from the predecessor in interest, if the predecessor
401 | has it or can get it with reasonable efforts.
402 |
403 | You may not impose any further restrictions on the exercise of the rights granted or
404 | affirmed under this License. For example, you may not impose a license fee, royalty,
405 | or other charge for exercise of rights granted under this License, and you may not
406 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging
407 | that any patent claim is infringed by making, using, selling, offering for sale, or
408 | importing the Program or any portion of it.
409 |
410 | ### 11. Patents
411 |
412 | A “contributor” is a copyright holder who authorizes use under this
413 | License of the Program or a work on which the Program is based. The work thus
414 | licensed is called the contributor's “contributor version”.
415 |
416 | A contributor's “essential patent claims” are all patent claims owned or
417 | controlled by the contributor, whether already acquired or hereafter acquired, that
418 | would be infringed by some manner, permitted by this License, of making, using, or
419 | selling its contributor version, but do not include claims that would be infringed
420 | only as a consequence of further modification of the contributor version. For
421 | purposes of this definition, “control” includes the right to grant patent
422 | sublicenses in a manner consistent with the requirements of this License.
423 |
424 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license
425 | under the contributor's essential patent claims, to make, use, sell, offer for sale,
426 | import and otherwise run, modify and propagate the contents of its contributor
427 | version.
428 |
429 | In the following three paragraphs, a “patent license” is any express
430 | agreement or commitment, however denominated, not to enforce a patent (such as an
431 | express permission to practice a patent or covenant not to sue for patent
432 | infringement). To “grant” such a patent license to a party means to make
433 | such an agreement or commitment not to enforce a patent against the party.
434 |
435 | If you convey a covered work, knowingly relying on a patent license, and the
436 | Corresponding Source of the work is not available for anyone to copy, free of charge
437 | and under the terms of this License, through a publicly available network server or
438 | other readily accessible means, then you must either **(1)** cause the Corresponding
439 | Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the
440 | patent license for this particular work, or **(3)** arrange, in a manner consistent with
441 | the requirements of this License, to extend the patent license to downstream
442 | recipients. “Knowingly relying” means you have actual knowledge that, but
443 | for the patent license, your conveying the covered work in a country, or your
444 | recipient's use of the covered work in a country, would infringe one or more
445 | identifiable patents in that country that you have reason to believe are valid.
446 |
447 | If, pursuant to or in connection with a single transaction or arrangement, you
448 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent
449 | license to some of the parties receiving the covered work authorizing them to use,
450 | propagate, modify or convey a specific copy of the covered work, then the patent
451 | license you grant is automatically extended to all recipients of the covered work and
452 | works based on it.
453 |
454 | A patent license is “discriminatory” if it does not include within the
455 | scope of its coverage, prohibits the exercise of, or is conditioned on the
456 | non-exercise of one or more of the rights that are specifically granted under this
457 | License. You may not convey a covered work if you are a party to an arrangement with
458 | a third party that is in the business of distributing software, under which you make
459 | payment to the third party based on the extent of your activity of conveying the
460 | work, and under which the third party grants, to any of the parties who would receive
461 | the covered work from you, a discriminatory patent license **(a)** in connection with
462 | copies of the covered work conveyed by you (or copies made from those copies), or **(b)**
463 | primarily for and in connection with specific products or compilations that contain
464 | the covered work, unless you entered into that arrangement, or that patent license
465 | was granted, prior to 28 March 2007.
466 |
467 | Nothing in this License shall be construed as excluding or limiting any implied
468 | license or other defenses to infringement that may otherwise be available to you
469 | under applicable patent law.
470 |
471 | ### 12. No Surrender of Others' Freedom
472 |
473 | If conditions are imposed on you (whether by court order, agreement or otherwise)
474 | that contradict the conditions of this License, they do not excuse you from the
475 | conditions of this License. If you cannot convey a covered work so as to satisfy
476 | simultaneously your obligations under this License and any other pertinent
477 | obligations, then as a consequence you may not convey it at all. For example, if you
478 | agree to terms that obligate you to collect a royalty for further conveying from
479 | those to whom you convey the Program, the only way you could satisfy both those terms
480 | and this License would be to refrain entirely from conveying the Program.
481 |
482 | ### 13. Use with the GNU Affero General Public License
483 |
484 | Notwithstanding any other provision of this License, you have permission to link or
485 | combine any covered work with a work licensed under version 3 of the GNU Affero
486 | General Public License into a single combined work, and to convey the resulting work.
487 | The terms of this License will continue to apply to the part which is the covered
488 | work, but the special requirements of the GNU Affero General Public License, section
489 | 13, concerning interaction through a network will apply to the combination as such.
490 |
491 | ### 14. Revised Versions of this License
492 |
493 | The Free Software Foundation may publish revised and/or new versions of the GNU
494 | General Public License from time to time. Such new versions will be similar in spirit
495 | to the present version, but may differ in detail to address new problems or concerns.
496 |
497 | Each version is given a distinguishing version number. If the Program specifies that
498 | a certain numbered version of the GNU General Public License “or any later
499 | version” applies to it, you have the option of following the terms and
500 | conditions either of that numbered version or of any later version published by the
501 | Free Software Foundation. If the Program does not specify a version number of the GNU
502 | General Public License, you may choose any version ever published by the Free
503 | Software Foundation.
504 |
505 | If the Program specifies that a proxy can decide which future versions of the GNU
506 | General Public License can be used, that proxy's public statement of acceptance of a
507 | version permanently authorizes you to choose that version for the Program.
508 |
509 | Later license versions may give you additional or different permissions. However, no
510 | additional obligations are imposed on any author or copyright holder as a result of
511 | your choosing to follow a later version.
512 |
513 | ### 15. Disclaimer of Warranty
514 |
515 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
516 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
517 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER
518 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
519 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
520 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
521 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
522 |
523 | ### 16. Limitation of Liability
524 |
525 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
526 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
527 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
528 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
529 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE
530 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
531 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
532 | POSSIBILITY OF SUCH DAMAGES.
533 |
534 | ### 17. Interpretation of Sections 15 and 16
535 |
536 | If the disclaimer of warranty and limitation of liability provided above cannot be
537 | given local legal effect according to their terms, reviewing courts shall apply local
538 | law that most closely approximates an absolute waiver of all civil liability in
539 | connection with the Program, unless a warranty or assumption of liability accompanies
540 | a copy of the Program in return for a fee.
541 |
542 | _END OF TERMS AND CONDITIONS_
543 |
544 | ## How to Apply These Terms to Your New Programs
545 |
546 | If you develop a new program, and you want it to be of the greatest possible use to
547 | the public, the best way to achieve this is to make it free software which everyone
548 | can redistribute and change under these terms.
549 |
550 | To do so, attach the following notices to the program. It is safest to attach them
551 | to the start of each source file to most effectively state the exclusion of warranty;
552 | and each file should have at least the “copyright” line and a pointer to
553 | where the full notice is found.
554 |
555 |
556 | Copyright (C)
557 |
558 | This program is free software: you can redistribute it and/or modify
559 | it under the terms of the GNU General Public License as published by
560 | the Free Software Foundation, either version 3 of the License, or
561 | (at your option) any later version.
562 |
563 | This program is distributed in the hope that it will be useful,
564 | but WITHOUT ANY WARRANTY; without even the implied warranty of
565 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
566 | GNU General Public License for more details.
567 |
568 | You should have received a copy of the GNU General Public License
569 | along with this program. If not, see .
570 |
571 | Also add information on how to contact you by electronic and paper mail.
572 |
573 | If the program does terminal interaction, make it output a short notice like this
574 | when it starts in an interactive mode:
575 |
576 | Copyright (C)
577 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'.
578 | This is free software, and you are welcome to redistribute it
579 | under certain conditions; type 'show c' for details.
580 |
581 | The hypothetical commands `show w` and `show c` should show the appropriate parts of
582 | the General Public License. Of course, your program's commands might be different;
583 | for a GUI interface, you would use an “about box”.
584 |
585 | You should also get your employer (if you work as a programmer) or school, if any, to
586 | sign a “copyright disclaimer” for the program, if necessary. For more
587 | information on this, and how to apply and follow the GNU GPL, see
588 | <>.
589 |
590 | The GNU General Public License does not permit incorporating your program into
591 | proprietary programs. If your program is a subroutine library, you may consider it
592 | more useful to permit linking proprietary applications with the library. If this is
593 | what you want to do, use the GNU Lesser General Public License instead of this
594 | License. But first, please read
595 | <>.
596 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nix-unit
2 |
3 | This project runs an attribute set of tests compatible with `lib.debug.runTests` while allowing individual attributes to fail.
4 |
5 | 
6 |
7 |
8 | ## Why use nix-unit?
9 |
10 | - Simple structure compatible with `lib.debug.runTests`
11 |
12 | - Allows individual test attributes to fail individually.
13 |
14 | Rather than evaluating the entire test suite in one go, serialise & compare `nix-unit` uses the Nix evaluator C++ API.
15 | Meaning that we can catch test failures individually, even if the failure is caused by an evaluation error.
16 |
17 | - Fast.
18 |
19 | No additional processing and coordination overhead caused by the external process approach.
20 |
21 | ## Comparison with other tools
22 | This comparison matrix was originally taken from [Unit test your Nix code](https://www.tweag.io/blog/2022-09-01-unit-test-your-nix-code/) but has been adapted.
23 | Pythonix is excluded as it's unmaintained.
24 |
25 | | Tool | Can test eval failures | Tests defined in Nix | in nixpkgs | snapshot testing(1) | Supports Lix |
26 | | ----------- | ---------------------- | -------------------- | ---------- |-------------------- | ------------ |
27 | | Nix-unit | yes | yes | yes | no | no |
28 | | Lix-unit | yes | yes | no | no | yes (2) |
29 | | runTests | no | yes | yes | no | yes |
30 | | Nixt | no | yes | no | no | yes |
31 | | Namaka | no | yes | yes | yes | ? |
32 |
33 | 1. [Snapshot testing](https://github.com/nix-community/namaka#snapshot-testing)
34 | 2. While lix-unit supports Lix, it does not support Nix, and vice versa.
35 |
36 | ## Using with Lix instead of Nix
37 |
38 | The Lix codebase has gone through significant changes, and it's not tenable to have a single code base that supports both implementations.
39 | Therefore nix-unit has been forked into [lix-unit](https://github.com/adisbladis/lix-unit)
40 |
41 | ## Documentation
42 |
43 | https://nix-community.github.io/nix-unit/
44 |
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | stdenv,
3 | lib,
4 | boost,
5 | clang-tools,
6 | cmake,
7 | difftastic,
8 | makeWrapper,
9 | meson,
10 | ninja,
11 | nix,
12 | nlohmann_json,
13 | pkg-config,
14 | }:
15 |
16 | let
17 | inherit (lib) fileset;
18 | src = lib.fileset.toSource {
19 | fileset = files;
20 | root = ./.;
21 | };
22 | files = fileset.unions [
23 | ./src
24 | ./meson.build
25 | ];
26 | in
27 | stdenv.mkDerivation {
28 | pname = "nix-unit";
29 | version = "2.24.1";
30 | inherit src;
31 | buildInputs = [
32 | nlohmann_json
33 | nix
34 | boost
35 | ];
36 | nativeBuildInputs = [
37 | makeWrapper
38 | meson
39 | pkg-config
40 | ninja
41 | # nlohmann_json can be only discovered via cmake files
42 | cmake
43 | ] ++ (lib.optional stdenv.cc.isClang [ clang-tools ]);
44 |
45 | postInstall = ''
46 | wrapProgram "$out/bin/nix-unit" --prefix PATH : ${difftastic}/bin
47 | '';
48 |
49 | meta = {
50 | description = "Nix unit test runner";
51 | homepage = "https://github.com/adisbladis/nix-unit";
52 | license = lib.licenses.gpl3;
53 | maintainers = with lib.maintainers; [ adisbladis ];
54 | platforms = lib.platforms.unix;
55 | mainProgram = "nix-unit";
56 | };
57 | }
58 |
--------------------------------------------------------------------------------
/dev/treefmt.nix:
--------------------------------------------------------------------------------
1 | {
2 | # Used to find the project root
3 | projectRootFile = "flake.lock";
4 |
5 | programs.clang-format.enable = true;
6 | programs.deadnix.enable = true;
7 | programs.nixfmt.enable = true;
8 | programs.ruff.format = true;
9 | programs.ruff.check = true;
10 | }
11 |
--------------------------------------------------------------------------------
/doc/.gitignore:
--------------------------------------------------------------------------------
1 | book
2 |
--------------------------------------------------------------------------------
/doc/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["adisbladis"]
3 | language = "en"
4 | multilingual = false
5 | src = "src"
6 | title = "Nix-unit"
7 |
8 | [preprocessor.cmdrun]
9 | command = "mdbook-cmdrun"
10 |
11 | [preprocessor.open-on-gh]
12 | command = "mdbook-open-on-gh"
13 | renderer = ["html"]
14 |
15 | [output.html]
16 | git-repository-url = "https://github.com/nix-community/nix-unit"
17 | git-branch = "main"
18 | open-on-text = """
19 |
20 | Found an issue? [Edit this page on GitHub.]
21 | """
22 |
--------------------------------------------------------------------------------
/doc/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | stdenv,
3 | nixdoc,
4 | self ? ../.,
5 | mdbook,
6 | mdbook-cmdrun,
7 | mdbook-open-on-gh,
8 | git,
9 | }:
10 |
11 | stdenv.mkDerivation {
12 | pname = "nix-unit-docs-html";
13 | version = "0.1";
14 | src = self;
15 | nativeBuildInputs = [
16 | nixdoc
17 | mdbook
18 | mdbook-open-on-gh
19 | mdbook-cmdrun
20 | git
21 | ];
22 |
23 | dontConfigure = true;
24 | dontFixup = true;
25 |
26 | env.RUST_BACKTRACE = 1;
27 |
28 | buildPhase = ''
29 | runHook preBuild
30 | cd doc
31 | chmod +w ../ && mkdir ../.git # Trick open-on-gh to find the git root
32 | mdbook build
33 | runHook postBuild
34 | '';
35 |
36 | installPhase = ''
37 | runHook preInstall
38 | mv book $out
39 | runHook postInstall
40 | '';
41 | }
42 |
--------------------------------------------------------------------------------
/doc/dev/highlightjs.nix:
--------------------------------------------------------------------------------
1 | # Highlightjs updater expression
2 | # This is used to build our custom highlight.js module
3 | let
4 | flake = builtins.getFlake (builtins.toString ../../.);
5 |
6 | pkgs = flake.inputs.nixpkgs.legacyPackages.${builtins.currentSystem};
7 |
8 | src = pkgs.fetchFromGitHub {
9 | owner = "highlightjs";
10 | repo = "highlight.js";
11 | rev = "10.7.3";
12 | hash = "sha256-6IW8WFlWdb0txEQxYvrLcAxMx/F5qGpxwUbWpTloFaY=";
13 | };
14 |
15 | npmlock2nix = pkgs.callPackage (pkgs.fetchFromGitHub {
16 | owner = "nix-community";
17 | repo = "npmlock2nix";
18 | rev = "9197bbf397d76059a76310523d45df10d2e4ca81";
19 | sha256 = "sha256-sJM82Sj8yfQYs9axEmGZ9Evzdv/kDcI9sddqJ45frrU=";
20 | }) { };
21 |
22 | in
23 | npmlock2nix.v2.build {
24 | inherit src;
25 | inherit (pkgs) nodejs;
26 | nativeBuildInputs = [ pkgs.git ];
27 | installPhase = ''
28 | cp -r build $out
29 | '';
30 | buildCommands = [
31 | "git init"
32 | ''git config user.email "you@example.com"''
33 | ''git config user.name "Your Name"''
34 | "git add $(ls | grep -v node_modules | grep -v extra)"
35 | "git commit -m 'Dummy commit'"
36 | "node tools/build.js"
37 | ];
38 | }
39 |
--------------------------------------------------------------------------------
/doc/dev/update_highlight.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | result=$(nix-build --no-out-link ./highlightjs.nix)
4 | cp -f "$result"/highlight.min.js ../theme/highlight.js
5 |
--------------------------------------------------------------------------------
/doc/src/FAQ.md:
--------------------------------------------------------------------------------
1 | # FAQ
2 |
3 | ## What about a watch mode?
4 | This adds a lot of additional complexity and for now is better dealt with by using external file watcher tools such as [Reflex](https://github.com/cespare/reflex) & [Watchman](https://facebook.github.io/watchman/).
5 |
6 | ## Can I change the colors?
7 |
8 | `nix-unit` uses [difftastic](https://github.com/wilfred/difftastic), which can be configured via environment variables. You can turn off
9 | colors via `DFT_COLOR=never`, give difftastic a hint for choosing better colors with `DFT_BACKGROUND=light` or see the full
10 | list of options via e.g. `nix run nixpkgs#difftastic -- --help`.
11 |
12 | ## Comparison with other tools
13 | This comparison matrix was originally taken from [Unit test your Nix code](https://www.tweag.io/blog/2022-09-01-unit-test-your-nix-code/) but has been adapted.
14 | Pythonix is excluded as it's unmaintained.
15 |
16 | | Tool | Can test eval failures | Tests defined in Nix | in nixpkgs | snapshot testing(1) |
17 | | ----------- | ---------------------- | -------------------- | ---------- |-------------------- |
18 | | Nix-unit | yes | yes | yes | no |
19 | | runTests | no | yes | yes | no |
20 | | Nixt | no | yes | no | no |
21 | | Namaka | no | yes | yes | yes |
22 |
23 | 1. [Snapshot testing](https://github.com/nix-community/namaka#snapshot-testing)
24 |
--------------------------------------------------------------------------------
/doc/src/HACKING.md:
--------------------------------------------------------------------------------
1 | # Hacking
2 |
3 | This document outlines hacking on `nix-unit` itself.
4 |
5 | ## Getting started
6 |
7 | To start hacking run either `nix-shell` (stable Nix) `nix develop` (Nix Flakes).
8 |
9 | Then create the meson build directory:
10 | ``` sh
11 | $ meson build
12 | $ cd build
13 | ```
14 |
15 | And use `ninja` to build:
16 | ``` sh
17 | $ ninja
18 | ```
19 | ## Formatter
20 |
21 | Before submitting a PR format the code with `nix fmt` and ensure Flake checks pass with `nix flake check`.
22 |
--------------------------------------------------------------------------------
/doc/src/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Contents
2 |
3 | - [Introduction](./introduction.md)
4 |
5 | # Examples
6 |
7 | - [Simple](./examples/simple.md)
8 | - [Flakes](./examples/flakes.md)
9 | - [Flake-parts](./examples/flake-parts.md)
10 | - [Trees](./examples/trees.md)
11 | - [Errors](./examples/errors.md)
12 |
13 | # Nix Reference documentation
14 |
15 | - [Coverage](./coverage.md)
16 |
17 | # Contributing
18 |
19 | - [Hacking](./HACKING.md)
20 | - [FAQ](./FAQ.md)
21 |
--------------------------------------------------------------------------------
/doc/src/coverage.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/doc/src/examples/errors.md:
--------------------------------------------------------------------------------
1 | # Testing errors
2 |
3 | While testing the happy path is a good start, you might also want to verify that expressions throw the error you expect. You check for a specifc type of error by setting `expectedError.type` and/or use `expectedError.msg` to search its message for the given regex.
4 |
5 | Example:
6 |
7 | `tests/default.nix`
8 | ``` nix
9 | {
10 | testCatchMessage = {
11 | expr = throw "10 instead of 5";
12 | expectedError.type = "ThrownError";
13 | expectedError.msg = "\\d+ instead of 5";
14 | };
15 | }
16 | ```
17 | ->
18 | ```
19 | ✅ testCatchMessage
20 |
21 | 🎉 1/1 successful
22 | ```
23 |
24 | > Note: Regular expression like the one above are supported
25 |
26 | ### Supported error types
27 |
28 | The following values for `expectedError.type` are valid:
29 |
30 | * `RestrictedPathError`
31 | * `MissingArgumentError`
32 | * `UndefinedVarError`
33 | * `TypeError`
34 | * `Abort`
35 | * `ThrownError`
36 | * `AssertionError`
37 | * `ParseError`
38 | * `EvalError`
39 |
--------------------------------------------------------------------------------
/doc/src/examples/flake-parts.md:
--------------------------------------------------------------------------------
1 | # Flake-parts
2 |
3 | `nix-unit` provides a [flake-parts](https://flake.parts) module for easy integration with flakes.
4 |
5 | You can write tests in the option [`flake.tests`] and/or [`perSystem.nix-unit.tests`].
6 |
7 | The module then takes care of setting up a [`checks`] derivation for you.
8 |
9 | For this to work, you may have to specify [`perSystem.nix-unit.inputs`] to make them available in the derivation.
10 | This tends to require that you flatten some of your [`inputs`] tree using `follows`.
11 |
12 | ## Example
13 |
14 | This example can be used with `nix flake init -t github:nix-community/nix-unit#flake-parts`.
15 |
16 | ```nix
17 |
18 | ```
19 |
20 | [`perSystem.nix-unit.inputs`]: https://flake.parts/options/nix-unit#opt-perSystem.nix-unit.inputs
21 | [`perSystem.nix-unit.tests`]: https://flake.parts/options/nix-unit#opt-perSystem.nix-unit.tests
22 | [`flake.tests`]: https://flake.parts/options/nix-unit#opt-flake.tests
23 | [`checks`]: https://flake.parts/options/flake-parts#opt-perSystem.checks
24 | [`inputs`]: https://nix.dev/manual/nix/latest/command-ref/new-cli/nix3-flake.html#flake-inputs
25 |
--------------------------------------------------------------------------------
/doc/src/examples/flakes.md:
--------------------------------------------------------------------------------
1 | # Flakes
2 |
3 | ## flake.nix
4 |
5 | Building on top of the simple classic example the same type of structure could also be expressed in a `flake.nix`:
6 | ``` nix
7 | {
8 | description = "A very basic flake using nix-unit";
9 |
10 | outputs = { self, nixpkgs }: {
11 | libTests = {
12 | testPass = {
13 | expr = 1;
14 | expected = 1;
15 | };
16 | };
17 | };
18 | }
19 |
20 | ```
21 |
22 | And is evaluated with `nix-unit` like so:
23 | ``` bash
24 | $ nix-unit --flake '.#libTests'
25 | ```
26 |
27 | ## flake checks
28 |
29 | Note: [flake-parts](./flake-parts.md) can manage this for you.
30 |
31 | You can also use `nix-unit` in flake checks ([link](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake-check)).
32 |
33 | Create a `tests` and `checks` outputs.
34 |
35 | ```nix
36 |
37 | ```
38 |
39 | Run `nix flake check` and get an error as expected.
40 |
41 | ```console
42 | error: builder for '/nix/store/73d58ybnyjql9ddy6lr7fprxijbgb78n-nix-unit-tests.drv' failed with exit code 1;
43 | last 10 log lines:
44 | > /build/nix-20-1/expected.nix --- 1/2 --- Nix
45 | > 1 3
46 | >
47 | > /build/nix-20-1/expected.nix --- 2/2 --- Nix
48 | > 1 4
49 | >
50 | >
51 | >
52 | > 😢 0/1 successful
53 | > error: Tests failed
54 | For full logs, run 'nix log /nix/store/73d58ybnyjql9ddy6lr7fprxijbgb78n-nix-unit-tests.drv'.
55 | ```
56 |
--------------------------------------------------------------------------------
/doc/src/examples/simple.md:
--------------------------------------------------------------------------------
1 | # Simple
2 |
3 | In it's simplest form a `nix-unit` test suite is just an attribute set where test attributes are prefix with `test`.
4 | Test attribute sets contain the keys `expr`, expressing the test & `expected`, expressing the expected results.
5 |
6 | An expression called `test.nix` containing:
7 | ``` nix
8 | {
9 | testPass = {
10 | expr = 1;
11 | expected = 1;
12 | };
13 |
14 | testFail = {
15 | expr = { x = 1; };
16 | expected = { y = 1; };
17 | };
18 |
19 | testFailEval = {
20 | expr = throw "NO U";
21 | expected = 0;
22 | };
23 | }
24 | ```
25 |
26 | Evaluated with `nix-unit`:
27 | ``` bash
28 | $ nix-unit test.nix
29 | ```
30 |
31 |
32 | Results in the output:
33 | ```
34 | ❌ testFail
35 | { x = 1; } != { y = 1; }
36 |
37 | ☢️ testFailEval
38 | error:
39 | … while calling the 'throw' builtin
40 |
41 | at /home/adisbladis/nix-eval-jobs/test.nix:13:12:
42 |
43 | 12| testFailEval = {
44 | 13| expr = throw "NO U";
45 | | ^
46 | 14| expected = 0;
47 |
48 | error: NO U
49 |
50 | ✅ testPass
51 |
52 | 😢 1/3 successful
53 | error: Tests failed
54 | ```
55 |
--------------------------------------------------------------------------------
/doc/src/examples/trees.md:
--------------------------------------------------------------------------------
1 | # Trees
2 |
3 | While simple flat attribute sets works you might want to express your tests as a deep attribute set.
4 | When `nix-unit` encounters an attribute which name is _not_ prefixed with `test` it recurses into that attribute to find more tests.
5 |
6 | Example:
7 | ``` nix
8 | {
9 | testPass = {
10 | expr = 1;
11 | expected = 1;
12 | };
13 |
14 | testFail = {
15 | expr = { x = 1; };
16 | expected = { y = 1; };
17 | };
18 |
19 | testFailEval = {
20 | expr = throw "NO U";
21 | expected = 0;
22 | };
23 |
24 | nested = {
25 | testFoo = {
26 | expr = "bar";
27 | expected = "bar";
28 | };
29 | };
30 | }
31 | ```
32 |
33 | Results in the output:
34 | ``` bash
35 | ✅ nested.testFoo
36 | ❌ testFail
37 | /run/user/1000/nix-244499-0/expected.nix --- Nix
38 | 1 { x = 1; } 1 { y = 1; }
39 |
40 |
41 | ☢️ testFailEval
42 | error:
43 | … while calling the 'throw' builtin
44 |
45 | at /home/adisbladis/sauce/github.com/nix-community/nix-unit/trees.nix:13:12:
46 |
47 | 12| testFailEval = {
48 | 13| expr = throw "NO U";
49 | | ^
50 | 14| expected = 0;
51 |
52 | error: NO U
53 |
54 | ✅ testPass
55 |
56 | 😢 2/4 successful
57 | error: Tests failed
58 | ```
59 |
--------------------------------------------------------------------------------
/doc/src/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | ## Why use nix-unit?
4 |
5 | - Simple structure compatible with `lib.debug.runTests`
6 |
7 | - Allows individual test attributes to fail individually.
8 |
9 | Rather than evaluating the entire test suite in one go, serialise & compare `nix-unit` uses the Nix evaluator C++ API.
10 | Meaning that we can catch test failures individually, even if the failure is caused by an evaluation error.
11 |
12 | - Fast.
13 |
14 | No additional processing and coordination overhead caused by the external process approach.
15 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-parts": {
4 | "inputs": {
5 | "nixpkgs-lib": [
6 | "nixpkgs"
7 | ]
8 | },
9 | "locked": {
10 | "lastModified": 1733312601,
11 | "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
12 | "owner": "hercules-ci",
13 | "repo": "flake-parts",
14 | "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
15 | "type": "github"
16 | },
17 | "original": {
18 | "owner": "hercules-ci",
19 | "repo": "flake-parts",
20 | "type": "github"
21 | }
22 | },
23 | "nix-github-actions": {
24 | "inputs": {
25 | "nixpkgs": [
26 | "nixpkgs"
27 | ]
28 | },
29 | "locked": {
30 | "lastModified": 1731952509,
31 | "narHash": "sha256-p4gB3Rhw8R6Ak4eMl8pqjCPOLCZRqaehZxdZ/mbFClM=",
32 | "owner": "nix-community",
33 | "repo": "nix-github-actions",
34 | "rev": "7b5f051df789b6b20d259924d349a9ba3319b226",
35 | "type": "github"
36 | },
37 | "original": {
38 | "owner": "nix-community",
39 | "repo": "nix-github-actions",
40 | "type": "github"
41 | }
42 | },
43 | "nixpkgs": {
44 | "locked": {
45 | "lastModified": 1733376361,
46 | "narHash": "sha256-aLJxoTDDSqB+/3orsulE6/qdlX6MzDLIITLZqdgMpqo=",
47 | "owner": "NixOS",
48 | "repo": "nixpkgs",
49 | "rev": "929116e316068c7318c54eb4d827f7d9756d5e9c",
50 | "type": "github"
51 | },
52 | "original": {
53 | "owner": "NixOS",
54 | "ref": "nixpkgs-unstable",
55 | "repo": "nixpkgs",
56 | "type": "github"
57 | }
58 | },
59 | "root": {
60 | "inputs": {
61 | "flake-parts": "flake-parts",
62 | "nix-github-actions": "nix-github-actions",
63 | "nixpkgs": "nixpkgs",
64 | "treefmt-nix": "treefmt-nix"
65 | }
66 | },
67 | "treefmt-nix": {
68 | "inputs": {
69 | "nixpkgs": [
70 | "nixpkgs"
71 | ]
72 | },
73 | "locked": {
74 | "lastModified": 1733662930,
75 | "narHash": "sha256-9qOp6jNdezzLMxwwXaXZWPXosHbNqno+f7Ii/xftqZ8=",
76 | "owner": "numtide",
77 | "repo": "treefmt-nix",
78 | "rev": "357cda84af1d74626afb7fb3bc12d6957167cda9",
79 | "type": "github"
80 | },
81 | "original": {
82 | "owner": "numtide",
83 | "repo": "treefmt-nix",
84 | "type": "github"
85 | }
86 | }
87 | },
88 | "root": "root",
89 | "version": 7
90 | }
91 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "Nix unit test runner";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
6 | flake-parts.url = "github:hercules-ci/flake-parts";
7 | flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
8 | treefmt-nix.url = "github:numtide/treefmt-nix";
9 | treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
10 | nix-github-actions.url = "github:nix-community/nix-github-actions";
11 | nix-github-actions.inputs.nixpkgs.follows = "nixpkgs";
12 | };
13 |
14 | outputs =
15 | inputs@{ flake-parts, nix-github-actions, ... }:
16 | let
17 | inherit (inputs.nixpkgs) lib;
18 | inherit (inputs) self;
19 | in
20 | flake-parts.lib.mkFlake { inherit inputs; } {
21 | systems = inputs.nixpkgs.lib.systems.flakeExposed;
22 | imports = [
23 | inputs.flake-parts.flakeModules.modules
24 | inputs.flake-parts.flakeModules.partitions
25 | ./lib/modules.nix
26 | ./templates/flake-module.nix
27 | ];
28 |
29 | flake.githubActions = nix-github-actions.lib.mkGithubMatrix {
30 | checks = {
31 | x86_64-linux = builtins.removeAttrs (self.packages.x86_64-linux // self.checks.x86_64-linux) [
32 | "default"
33 | ];
34 | x86_64-darwin = builtins.removeAttrs (self.packages.x86_64-darwin // self.checks.x86_64-darwin) [
35 | "default"
36 | "treefmt"
37 | ];
38 | };
39 | };
40 |
41 | flake.lib = import ./lib { inherit lib; };
42 |
43 | perSystem =
44 | {
45 | config,
46 | pkgs,
47 | self',
48 | ...
49 | }:
50 | let
51 | inherit (pkgs) stdenv;
52 | drvArgs = {
53 | nix = pkgs.nixVersions.nix_2_24;
54 | };
55 | in
56 | {
57 | packages.nix-unit = pkgs.callPackage ./default.nix drvArgs;
58 | packages.default = self'.packages.nix-unit;
59 | packages.doc = pkgs.callPackage ./doc {
60 | inherit self;
61 | };
62 | devShells.default =
63 | let
64 | pythonEnv = pkgs.python3.withPackages (_ps: [ ]);
65 | in
66 | pkgs.mkShell {
67 | nativeBuildInputs = self'.packages.nix-unit.nativeBuildInputs ++ [
68 | pythonEnv
69 | pkgs.difftastic
70 | pkgs.nixdoc
71 | pkgs.mdbook
72 | pkgs.mdbook-open-on-gh
73 | pkgs.mdbook-cmdrun
74 | config.treefmt.build.wrapper
75 | ];
76 | inherit (self'.packages.nix-unit) buildInputs;
77 | shellHook = lib.optionalString stdenv.isLinux ''
78 | export NIX_DEBUG_INFO_DIRS="${pkgs.curl.debug}/lib/debug:${drvArgs.nix.debug}/lib/debug''${NIX_DEBUG_INFO_DIRS:+:$NIX_DEBUG_INFO_DIRS}"
79 | export NIX_UNIT_OUTPATH=${self}
80 | '';
81 | };
82 | };
83 |
84 | # Extra things to load only when accessing development-specific attributes
85 | # such as `checks`
86 | partitionedAttrs.checks = "dev";
87 | partitionedAttrs.devShells = "dev";
88 | partitionedAttrs.tests = "dev"; # lib/modules/flake/dogfood.nix
89 | partitions.dev.module = {
90 | imports = [
91 | inputs.treefmt-nix.flakeModule
92 | self.modules.flake.default
93 | ./lib/modules/flake/dogfood.nix
94 | ];
95 | perSystem = {
96 | treefmt.imports = [ ./dev/treefmt.nix ];
97 | };
98 | };
99 | };
100 | }
101 |
--------------------------------------------------------------------------------
/lib/coverage.nix:
--------------------------------------------------------------------------------
1 | { lib }:
2 | let
3 | inherit (lib) toUpper substring stringLength;
4 |
5 | capitalise = s: toUpper (substring 0 1 s) + (substring 1 (stringLength s) s);
6 |
7 | in
8 | {
9 |
10 | /*
11 | Generate coverage testing for public interfaces.
12 |
13 | Example:
14 |
15 | # Expression
16 | let
17 | # The public interface (attrset) we are testing
18 | public = {
19 | addOne = x: x + 1;
20 | };
21 | # Test suite
22 | tests = {
23 | addOne = {
24 | testAdd = {
25 | expr = public.addOne 1;
26 | expected = 2;
27 | };
28 | };
29 | };
30 | in addCoverage public tests
31 |
32 | # Returns
33 | {
34 | addOne = {
35 | testAdd = {
36 | expected = 2;
37 | expr = 2;
38 | };
39 | };
40 | coverage = {
41 | testAddOne = {
42 | expected = true;
43 | expr = true;
44 | };
45 | };
46 | }
47 | */
48 | addCoverage =
49 | # The public interface to generate coverage for
50 | public:
51 | # Attribute set of tests to match agains
52 | tests:
53 | (
54 | assert !tests ? coverage;
55 | tests
56 | // {
57 | coverage = lib.mapAttrs' (n: _v: {
58 | name = "test" + (capitalise n);
59 | value = {
60 | expr = tests ? ${n};
61 | expected = true;
62 | };
63 | }) public;
64 | }
65 | );
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/lib/default.nix:
--------------------------------------------------------------------------------
1 | { lib }:
2 | {
3 | coverage = import ./coverage.nix { inherit lib; };
4 | }
5 |
--------------------------------------------------------------------------------
/lib/flake-checks/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-parts": {
4 | "inputs": {
5 | "nixpkgs-lib": [
6 | "nix-unit",
7 | "nixpkgs"
8 | ]
9 | },
10 | "locked": {
11 | "lastModified": 1730504689,
12 | "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=",
13 | "owner": "hercules-ci",
14 | "repo": "flake-parts",
15 | "rev": "506278e768c2a08bec68eb62932193e341f55c90",
16 | "type": "github"
17 | },
18 | "original": {
19 | "owner": "hercules-ci",
20 | "repo": "flake-parts",
21 | "type": "github"
22 | }
23 | },
24 | "nix-github-actions": {
25 | "inputs": {
26 | "nixpkgs": [
27 | "nix-unit",
28 | "nixpkgs"
29 | ]
30 | },
31 | "locked": {
32 | "lastModified": 1731952509,
33 | "narHash": "sha256-p4gB3Rhw8R6Ak4eMl8pqjCPOLCZRqaehZxdZ/mbFClM=",
34 | "owner": "nix-community",
35 | "repo": "nix-github-actions",
36 | "rev": "7b5f051df789b6b20d259924d349a9ba3319b226",
37 | "type": "github"
38 | },
39 | "original": {
40 | "owner": "nix-community",
41 | "repo": "nix-github-actions",
42 | "type": "github"
43 | }
44 | },
45 | "nix-unit": {
46 | "inputs": {
47 | "flake-parts": "flake-parts",
48 | "nix-github-actions": "nix-github-actions",
49 | "nixpkgs": [
50 | "nixpkgs"
51 | ],
52 | "treefmt-nix": "treefmt-nix"
53 | },
54 | "locked": {
55 | "lastModified": 1733344151,
56 | "narHash": "sha256-Zu31IAdl2Hp4bvsuyGkeuBp0igyvVHrmwN6bMfYTsIo=",
57 | "owner": "nix-community",
58 | "repo": "nix-unit",
59 | "rev": "6d373fdb344760d9c2e45994420b165b0202556b",
60 | "type": "github"
61 | },
62 | "original": {
63 | "owner": "nix-community",
64 | "repo": "nix-unit",
65 | "type": "github"
66 | }
67 | },
68 | "nixpkgs": {
69 | "locked": {
70 | "lastModified": 1733704289,
71 | "narHash": "sha256-uqhnOfrgQnBV3zXfZ9xnIQOhgi0nlFPMEMuWFYfb0UI=",
72 | "owner": "nixos",
73 | "repo": "nixpkgs",
74 | "rev": "68a4ce374c19f54acd2aaab13f63e07e9c213035",
75 | "type": "github"
76 | },
77 | "original": {
78 | "owner": "nixos",
79 | "repo": "nixpkgs",
80 | "type": "github"
81 | }
82 | },
83 | "root": {
84 | "inputs": {
85 | "nix-unit": "nix-unit",
86 | "nixpkgs": "nixpkgs"
87 | }
88 | },
89 | "treefmt-nix": {
90 | "inputs": {
91 | "nixpkgs": [
92 | "nix-unit",
93 | "nixpkgs"
94 | ]
95 | },
96 | "locked": {
97 | "lastModified": 1733222881,
98 | "narHash": "sha256-JIPcz1PrpXUCbaccEnrcUS8jjEb/1vJbZz5KkobyFdM=",
99 | "owner": "numtide",
100 | "repo": "treefmt-nix",
101 | "rev": "49717b5af6f80172275d47a418c9719a31a78b53",
102 | "type": "github"
103 | },
104 | "original": {
105 | "owner": "numtide",
106 | "repo": "treefmt-nix",
107 | "type": "github"
108 | }
109 | }
110 | },
111 | "root": "root",
112 | "version": 7
113 | }
114 |
--------------------------------------------------------------------------------
/lib/flake-checks/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:nixos/nixpkgs";
4 | nix-unit.url = "github:nix-community/nix-unit";
5 | nix-unit.inputs.nixpkgs.follows = "nixpkgs";
6 | };
7 | outputs =
8 | {
9 | self,
10 | nixpkgs,
11 | nix-unit,
12 | ...
13 | }:
14 | let
15 | forAllSystems = nixpkgs.lib.genAttrs [
16 | "x86_64-linux"
17 | "aarch64-linux"
18 | "x86_64-darwin"
19 | "x86_64-windows"
20 | ];
21 | in
22 | {
23 | tests.testPass = {
24 | expr = 3;
25 | expected = 4;
26 | };
27 |
28 | checks = forAllSystems (system: {
29 | default =
30 | nixpkgs.legacyPackages.${system}.runCommand "tests"
31 | {
32 | nativeBuildInputs = [ nix-unit.packages.${system}.default ];
33 | }
34 | ''
35 | export HOME="$(realpath .)"
36 | # The nix derivation must be able to find all used inputs in the nix-store because it cannot download it during buildTime.
37 | nix-unit --eval-store "$HOME" \
38 | --extra-experimental-features flakes \
39 | --override-input nixpkgs ${nixpkgs} \
40 | --flake ${self}#tests
41 | touch $out
42 | '';
43 | });
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/lib/modules.nix:
--------------------------------------------------------------------------------
1 | /**
2 | This module defines the `modules` flake output.
3 | */
4 |
5 | # nix-unit flake context
6 | { withSystem, ... }:
7 | {
8 | /**
9 | [flake-parts] modules.
10 |
11 | flake-parts: https://flake.parts
12 | */
13 | flake.modules.flake = {
14 | /**
15 | Provide only the `flake.tests` option, with rudimentary merging.
16 |
17 | Does not provide `nix flake check` support.
18 | */
19 | testsOutput = ./modules/flakes/tests-output.nix;
20 |
21 | /**
22 | Provides all functionality, including `nix flake check` support and system-specific tests.
23 | */
24 | default =
25 | # user flake context
26 | { lib, flake-parts-lib, ... }:
27 | {
28 | imports = [
29 | ./modules/flake/system.nix
30 | ./modules/flake/system-agnostic.nix
31 | ];
32 | options.perSystem = flake-parts-lib.mkPerSystemOption (
33 | # user flake perSystem
34 | { system, ... }:
35 | {
36 | options.nix-unit = {
37 | package = lib.mkOption {
38 | default = withSystem system ({ config, ... }: config.packages.nix-unit);
39 | defaultText = lib.literalMD ''
40 | package from the `nix-unit` flake, using that flake's `inputs.nixpkgs` (except for `follows`, etc)
41 | '';
42 | };
43 | };
44 | }
45 | );
46 | };
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/lib/modules/flake/dogfood.nix:
--------------------------------------------------------------------------------
1 | /**
2 | This module is loaded into the nix-unit flake sets up some example tests, to
3 | test the nix-unit flake modules.
4 | */
5 |
6 | { inputs, ... }:
7 | {
8 | perSystem = {
9 | nix-unit.inputs = {
10 | # inherit (inputs) nixpkgs flake-parts treefmt-nix;
11 | };
12 | nix-unit.allowNetwork = true;
13 | nix-unit.tests = {
14 | "test integer equality is reflexive" = {
15 | expr = "123";
16 | expected = "123";
17 | };
18 | "frobnicator" = {
19 | "testFoo" = {
20 | expr = "foo";
21 | expected = "foo";
22 | };
23 | };
24 | };
25 | };
26 | flake.tests.testBar = {
27 | expr = "bar";
28 | expected = "bar";
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/lib/modules/flake/system-agnostic.nix:
--------------------------------------------------------------------------------
1 | /**
2 | This module is responsible for testing the system-agnostic tests that may be
3 | defined in the flake's `tests` attribute (besides the `systems` attribute).
4 | */
5 |
6 | top@{ flake-parts-lib, lib, ... }:
7 | let
8 | inherit (lib) mkIf mkOption types;
9 | in
10 | {
11 | imports = [ ./tests-output.nix ];
12 | options.perSystem = flake-parts-lib.mkPerSystemOption (
13 | { config, ... }:
14 | {
15 | options.nix-unit = {
16 | enableSystemAgnostic = mkOption {
17 | default = true;
18 | type = types.bool;
19 | description = ''
20 | Copy system-agnostic tests from the `flake.tests` attribute into this system's tests.
21 |
22 | This ensures that the tests that are not defined in the system-specific tests are still run in `nix flake check`.
23 | '';
24 | };
25 | };
26 | config = mkIf config.nix-unit.enableSystemAgnostic {
27 | nix-unit.tests.system-agnostic = lib.removeAttrs top.config.flake.tests [ "systems" ];
28 | };
29 | }
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/lib/modules/flake/system.nix:
--------------------------------------------------------------------------------
1 | /**
2 | This module defines system-specific nix-unit integration logic, such as the
3 | `perSystem.nix-unit` options and the `checks`.
4 | */
5 |
6 | {
7 | config,
8 | lib,
9 | flake-parts-lib,
10 | self,
11 | ...
12 | }:
13 | let
14 | inherit (lib) mkOption types;
15 | inherit ((import ../types.nix { inherit lib; }).types) suite;
16 | overrideArg =
17 | name: value: "--override-input ${lib.escapeShellArg name} ${lib.escapeShellArg "${value}"}";
18 | in
19 | {
20 | imports = [ ./tests-output.nix ];
21 | options.perSystem = flake-parts-lib.mkPerSystemOption (
22 | {
23 | config,
24 | pkgs,
25 | system,
26 | ...
27 | }:
28 |
29 | let
30 | # Turn a derivation that writes `key` to `$out` into a check that's unique to the derivation.
31 | toNetworkedCheck =
32 | drv:
33 | let
34 | # key as in functional key
35 | key =
36 | # Discarding string context is safe, because we're not trying to read any store path contents.
37 | "check derived from ${baseNameOf (builtins.unsafeDiscardStringContext drv.drvPath)} is ok\n";
38 | in
39 | drv.overrideAttrs (old: {
40 | # To be written to $out by the builder
41 | inherit key;
42 | buildInputs = old.buildInputs or [ ] ++ [ pkgs.cacert ];
43 | outputHashAlgo = "sha256";
44 | outputHashMode = "flat";
45 | outputHash = builtins.hashString "sha256" key;
46 | });
47 | in
48 | {
49 | options.nix-unit = {
50 | package = mkOption {
51 | type = types.package;
52 | description = ''
53 | The nix-unit package to use.
54 | '';
55 | };
56 | inputs = mkOption {
57 | type = types.attrsOf types.path;
58 | default = { };
59 | description = ''
60 | Input overrides to pass to nix-unit.
61 |
62 | Since nix-unit will be invoked in the nix sandbox, any flake inputs
63 | that are required for the tests must be passed here.
64 |
65 | This prevents the need to download these inputs for each run.
66 | '';
67 | example = lib.literalExpression ''
68 | {
69 | inherit (inputs) nixpkgs flake-parts;
70 | }
71 | '';
72 | };
73 | allowNetwork = mkOption {
74 | type = types.bool;
75 | default = false;
76 | description = ''
77 | Whether to allow network access in the nix-unit tests.
78 | This is useful for tests that depend on fetched sources, as is often the case with flake inputs.
79 | `nix-unit.inputs` may also be a solution, and tends to perform better.
80 | Both solutions can be combined.
81 | '';
82 | };
83 | tests = mkOption {
84 | type = suite;
85 | default = { };
86 | description = ''
87 | A nix-unit test suite; as [introduced in the manual](https://nix-community.github.io/nix-unit/examples/simple.html).
88 | '';
89 | example = lib.literalExpression ''
90 | {
91 | "test integer equality is reflexive" = {
92 | expr = "123";
93 | expected = "123";
94 | };
95 | "frobnicator" = {
96 | "testFoo" = {
97 | expr = "foo";
98 | expected = "foo";
99 | };
100 | }
101 | }
102 | '';
103 | };
104 | };
105 | config = {
106 | checks.nix-unit = (if config.nix-unit.allowNetwork then toNetworkedCheck else x: x) (
107 | pkgs.runCommandNoCC "nix-unit-check"
108 | {
109 | nativeBuildInputs = [ config.nix-unit.package ];
110 | # For toNetworkedCheck to override
111 | key = "";
112 | }
113 | ''
114 | export HOME="$(realpath .)"
115 | echo "Running tests for " ${lib.escapeShellArg system}
116 | nix-unit --eval-store "$HOME" \
117 | --show-trace \
118 | --extra-experimental-features flakes \
119 | ${lib.concatStringsSep "\\\n " (lib.mapAttrsToList overrideArg config.nix-unit.inputs)} \
120 | --flake ${self}#tests.systems.${system} \
121 | ;
122 | echo -n "$key" > $out
123 | ''
124 | );
125 | };
126 | }
127 | );
128 | config = {
129 | flake = {
130 | tests.systems = lib.mapAttrs (_system: config: config.nix-unit.tests) config.allSystems;
131 | };
132 | };
133 | }
134 |
--------------------------------------------------------------------------------
/lib/modules/flake/tests-output.nix:
--------------------------------------------------------------------------------
1 | { lib, ... }:
2 | let
3 | inherit (lib) mkOption;
4 | inherit ((import ../types.nix { inherit lib; }).types) suite;
5 | in
6 | {
7 | options = {
8 | flake.tests = mkOption {
9 | type = suite;
10 | default = { };
11 | description = ''
12 | A nix-unit test suite; as [introduced in the manual](https://nix-community.github.io/nix-unit/examples/simple.html).
13 | '';
14 | example = lib.literalExpression ''
15 | {
16 | "test integer equality is reflexive" = {
17 | expr = "123";
18 | expected = "123";
19 | };
20 | "frobnicator" = {
21 | "testFoo" = {
22 | expr = "foo";
23 | expected = "foo";
24 | };
25 | }
26 | }
27 | '';
28 | };
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/lib/modules/types.nix:
--------------------------------------------------------------------------------
1 | { lib }:
2 |
3 | /**
4 | Module system types for nix-unit.
5 | */
6 | {
7 | /**
8 | A nix-unit test suite as [introduced in the manual](https://nix-community.github.io/nix-unit/examples/simple.html).
9 |
10 | A more advanced type could be provided, but this would require
11 | - optional fields
12 | - e.g. https://github.com/NixOS/nixpkgs/pull/334680
13 | - name-dependent attribute value types.
14 | - e.g. `itemTypeFunction` behavior in https://github.com/NixOS/nixpkgs/pull/344216#issuecomment-2373878236
15 |
16 | The value of type-checking is dubious, and we don't know how well the
17 | documentation tooling would render this complicated type.
18 | */
19 | # This type could be refined, to allow more sophisticated merging, but for
20 | # now, merging only the top-level keys seems sufficient.
21 | types.suite = lib.types.lazyAttrsOf lib.types.raw;
22 | }
23 |
--------------------------------------------------------------------------------
/lib/test_coverage.nix:
--------------------------------------------------------------------------------
1 | _:
2 |
3 | {
4 | }
5 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('nix-unit', 'cpp',
2 | version : '0.1.0',
3 | license : 'GPL-3.0',
4 | )
5 |
6 | nix_main_dep = dependency('nix-main', required: true)
7 | nix_store_dep = dependency('nix-store', required: true)
8 | nix_expr_dep = dependency('nix-expr', required: true)
9 | nix_cmd_dep = dependency('nix-cmd', required: true)
10 | nix_flake_dep = dependency('nix-flake', required: true)
11 | threads_dep = dependency('threads', required: true)
12 | nlohmann_json_dep = dependency('nlohmann_json', required: true)
13 | boost_dep = dependency('boost', required: true)
14 |
15 | subdir('src')
16 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:base"
5 | ],
6 | "lockFileMaintenance": {
7 | "enabled": true,
8 | "extends": [
9 | "schedule:weekly"
10 | ]
11 | },
12 | "nix": {
13 | "enabled": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | src = [
2 | 'nix-unit.cc',
3 | ]
4 |
5 |
6 | executable('nix-unit', src,
7 | dependencies : [
8 | nix_main_dep,
9 | nix_store_dep,
10 | nix_expr_dep,
11 | nix_cmd_dep,
12 | nix_flake_dep,
13 | boost_dep,
14 | nlohmann_json_dep,
15 | threads_dep
16 | ],
17 | install: true,
18 | cpp_args: ['-std=c++2a'])
19 |
--------------------------------------------------------------------------------
/src/nix-unit.cc:
--------------------------------------------------------------------------------
1 | #include