Merge pull request #234 from perillo/improve-ci

Improve CI
This commit is contained in:
Chris Boesch 2023-04-19 15:25:12 +02:00 committed by GitHub
commit 2ed24ab3cf
27 changed files with 290 additions and 101 deletions

25
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: CI
on:
pull_request:
branches: [ main ]
defaults:
run:
shell: bash
jobs:
compat:
runs-on: ubuntu-latest
strategy:
matrix:
zig: [ 0.6.0, 0.7.0, 0.8.0, 0.9.0, 0.10.0 ]
steps:
- uses: actions/checkout@v2
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
with:
version: ${{ matrix.zig }}
- name: Check compatibility with old Zig compilers
run: ci/compat.sh

25
ci/compat.sh Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
# This script checks that `zig build` will return an useful error message when
# the Zig compiler is not compatible, instead of failing due to a syntax error.
#
# This script should be run on an UNIX system.
zig_version=$(zig version)
zig build -Dn=1 -Dhealed &> /dev/null 2>&1
zig_ret=$?
if [ "$zig_ret" -eq 0 ]; then
printf "zig %s unexpectedly succeeded\n" "$zig_version"
exit 1
fi
zig_error=$(zig build -Dn=1 -Dhealed 2>&1)
echo "$zig_error" | grep -q "it looks like your version of zig is too old"
zig_ret=$?
if [ "$zig_ret" -ne 0 ]; then
printf "zig %s is not compatible\n" "$zig_version"
exit 1
fi

View file

@ -107,7 +107,7 @@ const Path = struct {
const a_paths = [_]Path{ const a_paths = [_]Path{
Path{ Path{
.from = &a, // from: Archer's Point .from = &a, // from: Archer's Point
.to = &b, // to: Bridge .to = &b, // to: Bridge
.dist = 2, .dist = 2,
}, },
}; };
@ -115,12 +115,12 @@ const a_paths = [_]Path{
const b_paths = [_]Path{ const b_paths = [_]Path{
Path{ Path{
.from = &b, // from: Bridge .from = &b, // from: Bridge
.to = &a, // to: Archer's Point .to = &a, // to: Archer's Point
.dist = 2, .dist = 2,
}, },
Path{ Path{
.from = &b, // from: Bridge .from = &b, // from: Bridge
.to = &d, // to: Dogwood Grove .to = &d, // to: Dogwood Grove
.dist = 1, .dist = 1,
}, },
}; };
@ -128,12 +128,12 @@ const b_paths = [_]Path{
const c_paths = [_]Path{ const c_paths = [_]Path{
Path{ Path{
.from = &c, // from: Cottage .from = &c, // from: Cottage
.to = &d, // to: Dogwood Grove .to = &d, // to: Dogwood Grove
.dist = 3, .dist = 3,
}, },
Path{ Path{
.from = &c, // from: Cottage .from = &c, // from: Cottage
.to = &e, // to: East Pond .to = &e, // to: East Pond
.dist = 2, .dist = 2,
}, },
}; };
@ -141,17 +141,17 @@ const c_paths = [_]Path{
const d_paths = [_]Path{ const d_paths = [_]Path{
Path{ Path{
.from = &d, // from: Dogwood Grove .from = &d, // from: Dogwood Grove
.to = &b, // to: Bridge .to = &b, // to: Bridge
.dist = 1, .dist = 1,
}, },
Path{ Path{
.from = &d, // from: Dogwood Grove .from = &d, // from: Dogwood Grove
.to = &c, // to: Cottage .to = &c, // to: Cottage
.dist = 3, .dist = 3,
}, },
Path{ Path{
.from = &d, // from: Dogwood Grove .from = &d, // from: Dogwood Grove
.to = &f, // to: Fox Pond .to = &f, // to: Fox Pond
.dist = 7, .dist = 7,
}, },
}; };
@ -159,20 +159,20 @@ const d_paths = [_]Path{
const e_paths = [_]Path{ const e_paths = [_]Path{
Path{ Path{
.from = &e, // from: East Pond .from = &e, // from: East Pond
.to = &c, // to: Cottage .to = &c, // to: Cottage
.dist = 2, .dist = 2,
}, },
Path{ Path{
.from = &e, // from: East Pond .from = &e, // from: East Pond
.to = &f, // to: Fox Pond .to = &f, // to: Fox Pond
.dist = 1, // (one-way down a short waterfall!) .dist = 1, // (one-way down a short waterfall!)
}, },
}; };
const f_paths = [_]Path{ const f_paths = [_]Path{
Path{ Path{
.from = &f, // from: Fox Pond .from = &f, // from: Fox Pond
.to = &d, // to: Dogwood Grove .to = &d, // to: Dogwood Grove
.dist = 7, .dist = 7,
}, },
}; };
@ -355,8 +355,8 @@ pub fn main() void {
// Here's where the hermit decides where he would like to go. Once // Here's where the hermit decides where he would like to go. Once
// you get the program working, try some different Places on the // you get the program working, try some different Places on the
// map! // map!
const start = &a; // Archer's Point const start = &a; // Archer's Point
const destination = &f; // Fox Pond const destination = &f; // Fox Pond
// Store each Path array as a slice in each Place. As mentioned // Store each Path array as a slice in each Place. As mentioned
// above, we needed to delay making these references to avoid // above, we needed to delay making these references to avoid

View file

@ -18,10 +18,10 @@
const print = @import("std").debug.print; const print = @import("std").debug.print;
pub fn main() void { pub fn main() void {
var zig = [_]u8 { var zig = [_]u8{
0o131, // octal 0o131, // octal
0b1101000, // binary 0b1101000, // binary
0x66, // hex 0x66, // hex
}; };
print("{s} is cool.\n", .{zig}); print("{s} is cool.\n", .{zig});

View file

@ -16,7 +16,7 @@ const print = @import("std").debug.print;
pub fn main() void { pub fn main() void {
// Here we declare arrays of three different types and sizes // Here we declare arrays of three different types and sizes
// at compile time from a function call. Neat! // at compile time from a function call. Neat!
const s1 = makeSequence(u8, 3); // creates a [3]u8 const s1 = makeSequence(u8, 3); // creates a [3]u8
const s2 = makeSequence(u32, 5); // creates a [5]u32 const s2 = makeSequence(u32, 5); // creates a [5]u32
const s3 = makeSequence(i64, 7); // creates a [7]i64 const s3 = makeSequence(i64, 7); // creates a [7]i64

View file

@ -151,8 +151,8 @@ const HermitsNotebook = struct {
}; };
pub fn main() void { pub fn main() void {
const start = &a; // Archer's Point const start = &a; // Archer's Point
const destination = &f; // Fox Pond const destination = &f; // Fox Pond
// We could either have this: // We could either have this:
// //

View file

@ -18,8 +18,8 @@ pub fn main() void {
// //
// Don't change this part: // Don't change this part:
// //
// = .{'h', 'e', 'l', 'l', 'o'}; // = .{ 'h', 'e', 'l', 'l', 'o' };
// //
const hello = .{'h', 'e', 'l', 'l', 'o'}; const hello = .{ 'h', 'e', 'l', 'l', 'o' };
print("I say {s}!\n", .{hello}); print("I say {s}!\n", .{hello});
} }

View file

@ -99,7 +99,7 @@ pub fn main() !void {
var my_insects = [_]Insect{ var my_insects = [_]Insect{
Insect{ .ant = Ant{ .still_alive = true } }, Insect{ .ant = Ant{ .still_alive = true } },
Insect{ .bee = Bee{ .flowers_visited = 17 } }, Insect{ .bee = Bee{ .flowers_visited = 17 } },
Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 }, }, Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
}; };
std.debug.print("Daily Insect Report:\n", .{}); std.debug.print("Daily Insect Report:\n", .{});

View file

@ -42,5 +42,8 @@ do
fi fi
done done
# Check the healed exercises formatting.
zig fmt --check patches/healed
# Test the healed exercises. May the compiler have mercy upon us. # Test the healed exercises. May the compiler have mercy upon us.
zig build -Dhealed zig build -Dhealed

View file

@ -5,6 +5,6 @@
> if (err == MyNumberError.TooSmall) { > if (err == MyNumberError.TooSmall) {
> return 10; > return 10;
> } > }
> >
> return err; > return err;
> }; > };

View file

@ -1,4 +1,4 @@
22c22 26c26
< stdout.print("Hello world!\n", .{}); < stdout.print("Hello world!\n", .{});
--- ---
> try stdout.print("Hello world!\n", .{}); > try stdout.print("Hello world!\n", .{});

View file

@ -1,4 +1,4 @@
35c34 35c35
< std.debug.print("failed!\n", .{}); < std.debug.print("failed!\n", .{});
--- ---
> errdefer std.debug.print("failed!\n", .{}); > errdefer std.debug.print("failed!\n", .{});

View file

@ -1,4 +1,4 @@
24c24 26c26
< const b: *u8 = &a; // fix this! < const b: *u8 = &a; // fix this!
--- ---
> const b: *const u8 = &a; // fix this! > const b: *const u8 = &a; // fix this!

View file

@ -1,8 +1,8 @@
24c12 24c24
< tail: *Elephant = null, // Hmm... tail needs something... < tail: *Elephant = null, // Hmm... tail needs something...
--- ---
> tail: ?*Elephant = null, // <---- make this optional! > tail: ?*Elephant = null, // <---- make this optional!
54c42 54c54
< if (e.tail == null) ???; < if (e.tail == null) ???;
--- ---
> if (e.tail == null) break; > if (e.tail == null) break;

View file

@ -6,7 +6,7 @@
< var first_line2: Err!*const [21]u8 = ???; < var first_line2: Err!*const [21]u8 = ???;
--- ---
> var first_line2: Err!*const [21]u8 = Err.Cthulhu; > var first_line2: Err!*const [21]u8 = Err.Cthulhu;
79,80c79,80 80,81c80,81
< fn printSecondLine() ??? { < fn printSecondLine() ??? {
< var second_line2: ?*const [18]u8 = ???; < var second_line2: ?*const [18]u8 = ???;
--- ---

View file

@ -1,8 +1,8 @@
22,24c22,24 22,24c22,24
< 0o131, // octal < 0o131, // octal
< 0b1101000, // binary < 0b1101000, // binary
< 0x66, // hex < 0x66, // hex
--- ---
> 0o132, // octal > 0o132, // octal
> 0b1101001, // binary > 0b1101001, // binary
> 0x67, // hex > 0x67, // hex

View file

@ -1,8 +1,8 @@
72c72 67c67
< const expected_result: u8 = ???; < const expected_result: u8 = ???;
--- ---
> const expected_result: u8 = 0b00010010; > const expected_result: u8 = 0b00010010;
88c88 82c82
< const tupni: u8 = @bitReverse(input, tupni); < const tupni: u8 = @bitReverse(input, tupni);
--- ---
> const tupni: u8 = @bitReverse(input); > const tupni: u8 = @bitReverse(input);

View file

@ -1,39 +1,20 @@
--- exercises/065_builtins2.zig 61c61
+++ answers/065_builtins2.zig < narcissus.??? = ???;
@@ -58,7 +58,7 @@ ---
// Oops! We cannot leave the 'me' and 'myself' fields > narcissus.myself = &narcissus;
// undefined. Please set them here: 73c73
narcissus.me = &narcissus; < const Type2 = narcissus.fetchTheMostBeautifulType();
- narcissus.??? = ???; ---
+ narcissus.myself = &narcissus; > const Type2 = Narcissus.fetchTheMostBeautifulType();
112c112
// This determines a "peer type" from three separate < if (fields[0].??? != void) {
// references (they just happen to all be the same object). ---
@@ -70,7 +70,7 @@ > if (fields[0].type != void) {
// 116c116
// The fix for this is very subtle, but it makes a big < if (fields[1].??? != void) {
// difference! ---
- const Type2 = narcissus.fetchTheMostBeautifulType(); > if (fields[1].type != void) {
+ const Type2 = Narcissus.fetchTheMostBeautifulType(); 120c120
< if (fields[2].??? != void) {
// Now we print a pithy statement about Narcissus. ---
print("A {s} loves all {s}es. ", .{ > if (fields[2].type != void) {
@@ -109,15 +109,15 @@
// Please complete these 'if' statements so that the field
// name will not be printed if the field is of type 'void'
// (which is a zero-bit type that takes up no space at all!):
- if (fields[0].??? != void) {
+ if (fields[0].type != void) {
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[0].name});
}
- if (fields[1].??? != void) {
+ if (fields[1].type != void) {
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[1].name});
}
- if (fields[2].??? != void) {
+ if (fields[2].type != void) {
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[2].name});
}

View file

@ -1,4 +1,4 @@
64,65c64,65 65,66c65,66
< var var_int = 12345; < var var_int = 12345;
< var var_float = 987.654; < var var_float = 987.654;
--- ---

View file

@ -1,8 +1,8 @@
85c84 85c85
< for (???) |s| { < for (???) |s| {
--- ---
> for (my_seq) |s| { > for (my_seq) |s| {
97c96 97c97
< while (??? != my_sentinel) { < while (??? != my_sentinel) {
--- ---
> while (my_seq[i] != my_sentinel) { > while (my_seq[i] != my_sentinel) {

View file

@ -1,18 +1,8 @@
--- exercises/080_anonymous_structs.zig 51c51
+++ answers/080_anonymous_structs.zig < var circle1 = ??? {
@@ -48,13 +48,13 @@ ---
// * circle1 should hold i32 integers > var circle1 = Circle(i32){
// * circle2 should hold f32 floats 57c57
// < var circle2 = ??? {
- var circle1 = ??? { ---
+ var circle1 = Circle(i32){ > var circle2 = Circle(f32){
.center_x = 25,
.center_y = 70,
.radius = 15,
};
- var circle2 = ??? {
+ var circle2 = Circle(f32){
.center_x = 25.234,
.center_y = 70.999,
.radius = 15.714,

View file

@ -1,4 +1,4 @@
23c23 23c23
< const hello = .{'h', 'e', 'l', 'l', 'o'}; < const hello = .{ 'h', 'e', 'l', 'l', 'o' };
--- ---
> const hello: [5]u8 = .{'h', 'e', 'l', 'l', 'o'}; > const hello: [5]u8 = .{ 'h', 'e', 'l', 'l', 'o' };

View file

@ -1,4 +1,4 @@
65c65 66c66
< var avg: []f64 = ???; < var avg: []f64 = ???;
--- ---
> var avg: []f64 = try allocator.alloc(f64, arr.len); > var avg: []f64 = try allocator.alloc(f64, arr.len);

View file

@ -1,4 +1,4 @@
82c82 83c83
< ???; < ???;
--- ---
> x ^= y; > x ^= y;

View file

@ -1,4 +1,4 @@
62c62 63c63
< return bits == 0x..???; < return bits == 0x..???;
--- ---
> return bits == 0x3ffffff; > return bits == 0x3ffffff;

97
tools/check-exercises.py Executable file
View file

@ -0,0 +1,97 @@
#!/usr/bin/env python
import difflib
import io
import os
import os.path
import subprocess
import sys
IGNORE = subprocess.DEVNULL
PIPE = subprocess.PIPE
EXERCISES_PATH = "exercises"
HEALED_PATH = "patches/healed"
PATCHES_PATH = "patches/patches"
# Heals all the exercises.
def heal():
maketree(HEALED_PATH)
with os.scandir(EXERCISES_PATH) as it:
for entry in it:
name = entry.name
original_path = entry.path
patch_path = os.path.join(PATCHES_PATH, patch_name(name))
output_path = os.path.join(HEALED_PATH, name)
patch(original_path, patch_path, output_path)
# Yields all the healed exercises that are not correctly formatted.
def check_healed():
term = subprocess.run(
["zig", "fmt", "--check", HEALED_PATH], stdout=PIPE, text=True
)
if term.stdout == "" and term.returncode != 0:
term.check_returncode()
stream = io.StringIO(term.stdout)
for line in stream:
yield line.strip()
def main():
heal()
# Show the unified diff between the original example and the correctly
# formatted one.
for i, original in enumerate(check_healed()):
if i > 0:
print()
name = os.path.basename(original)
print(f"checking exercise {name}...\n")
from_file = open(original)
to_file = zig_fmt_file(original)
diff = difflib.unified_diff(
from_file.readlines(), to_file.readlines(), name, name + "-fmt"
)
sys.stderr.writelines(diff)
def maketree(path):
return os.makedirs(path, exist_ok=True)
# Returns path with the patch extension.
def patch_name(path):
name, _ = os.path.splitext(path)
return name + ".patch"
# Applies patch to original, and write the file to output.
def patch(original, patch, output):
subprocess.run(
["patch", "-i", patch, "-o", output, original], stdout=IGNORE, check=True
)
# Formats the Zig file at path, and returns the possibly reformatted file as a
# file object.
def zig_fmt_file(path):
with open(path) as stdin:
term = subprocess.run(
["zig", "fmt", "--stdin"], stdin=stdin, stdout=PIPE, check=True, text=True
)
return io.StringIO(term.stdout)
main()

68
tools/update-patches.py Executable file
View file

@ -0,0 +1,68 @@
#!/usr/bin/env python
import os
import os.path
import subprocess
IGNORE = subprocess.DEVNULL
EXERCISES_PATH = "exercises"
ANSWERS_PATH = "answers"
PATCHES_PATH = "patches/patches"
# Heals all the exercises.
def heal():
maketree(ANSWERS_PATH)
with os.scandir(EXERCISES_PATH) as it:
for entry in it:
name = entry.name
original_path = entry.path
patch_path = os.path.join(PATCHES_PATH, patch_name(name))
output_path = os.path.join(ANSWERS_PATH, name)
patch(original_path, patch_path, output_path)
def main():
heal()
with os.scandir(EXERCISES_PATH) as it:
for entry in it:
name = entry.name
broken_path = entry.path
healed_path = os.path.join(ANSWERS_PATH, name)
patch_path = os.path.join(PATCHES_PATH, patch_name(name))
with open(patch_path, "w") as file:
term = subprocess.run(
["diff", broken_path, healed_path],
stdout=file,
text=True,
)
assert term.returncode == 1
def maketree(path):
return os.makedirs(path, exist_ok=True)
# Returns path with the patch extension.
def patch_name(path):
name, _ = os.path.splitext(path)
return name + ".patch"
# Applies patch to original, and write the file to output.
def patch(original, patch, output):
subprocess.run(
["patch", "-i", patch, "-o", output, original], stdout=IGNORE, check=True
)
main()