Programming Language

Nocter

A self-contained systems language built around simplicity, encapsulation, and foolproof design.

tests.nct

see ./index.nct

use std/io.BlockingReader
use std/vec.Vec

struct FragmentedSource {
    bytes: Vec<u8>
    next: usize
}

instance FragmentedSource {
    blocking method &+self.read_blocking(output: &+[u8]): usize! {
        if output.len() == 0 || self.next == self.bytes.len() { return 0 }
        output[0] = self.bytes[self.next]
        self.next += 1
        return 1
    }
}

func valid_archive(): Vec<u8> {
    return Vec [
        u8.truncate(31),
        u8.truncate(139),
        u8.truncate(8),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(2),
        u8.truncate(255),
        u8.truncate(237),
        u8.truncate(205),
        u8.truncate(49),
        u8.truncate(10),
        u8.truncate(132),
        u8.truncate(64),
        u8.truncate(16),
        u8.truncate(4),
        u8.truncate(192),
        u8.truncate(121),
        u8.truncate(138),
        u8.truncate(47),
        u8.truncate(56),
        u8.truncate(68),
        u8.truncate(68),
        u8.truncate(223),
        u8.truncate(35),
        u8.truncate(122),
        u8.truncate(169),
        u8.truncate(194),
        u8.truncate(185),
        u8.truncate(130),
        u8.truncate(207),
        u8.truncate(119),
        u8.truncate(53),
        u8.truncate(146),
        u8.truncate(203),
        u8.truncate(21),
        u8.truncate(196),
        u8.truncate(170),
        u8.truncate(164),
        u8.truncate(135),
        u8.truncate(238),
        u8.truncate(96),
        u8.truncate(186),
        u8.truncate(79),
        u8.truncate(90),
        u8.truncate(83),
        u8.truncate(92),
        u8.truncate(171),
        u8.truncate(204),
        u8.truncate(154),
        u8.truncate(186),
        u8.truncate(62),
        u8.truncate(50),
        u8.truncate(251),
        u8.truncate(207),
        u8.truncate(125),
        u8.truncate(61),
        u8.truncate(221),
        u8.truncate(123),
        u8.truncate(223),
        u8.truncate(180),
        u8.truncate(85),
        u8.truncate(21),
        u8.truncate(69),
        u8.truncate(25),
        u8.truncate(55),
        u8.truncate(88),
        u8.truncate(230),
        u8.truncate(212),
        u8.truncate(253),
        u8.truncate(242),
        u8.truncate(203),
        u8.truncate(120),
        u8.truncate(167),
        u8.truncate(249),
        u8.truncate(219),
        u8.truncate(79),
        u8.truncate(227),
        u8.truncate(16),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(60),
        u8.truncate(204),
        u8.truncate(6),
        u8.truncate(189),
        u8.truncate(244),
        u8.truncate(210),
        u8.truncate(47),
        u8.truncate(0),
        u8.truncate(40),
        u8.truncate(0),
        u8.truncate(0),
    ]
}

func empty_archive(): Vec<u8> {
    return Vec [
        u8.truncate(31),
        u8.truncate(139),
        u8.truncate(8),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(2),
        u8.truncate(255),
        u8.truncate(99),
        u8.truncate(96),
        u8.truncate(24),
        u8.truncate(5),
        u8.truncate(163),
        u8.truncate(96),
        u8.truncate(20),
        u8.truncate(140),
        u8.truncate(84),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(46),
        u8.truncate(175),
        u8.truncate(181),
        u8.truncate(239),
        u8.truncate(0),
        u8.truncate(4),
        u8.truncate(0),
        u8.truncate(0),
    ]
}

func archive_with_trailing_decoded_bytes(): Vec<u8> {
    return Vec [
        u8.truncate(31),
        u8.truncate(139),
        u8.truncate(8),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(2),
        u8.truncate(255),
        u8.truncate(99),
        u8.truncate(96),
        u8.truncate(24),
        u8.truncate(5),
        u8.truncate(163),
        u8.truncate(96),
        u8.truncate(20),
        u8.truncate(140),
        u8.truncate(130),
        u8.truncate(81),
        u8.truncate(48),
        u8.truncate(18),
        u8.truncate(1),
        u8.truncate(0),
        u8.truncate(126),
        u8.truncate(225),
        u8.truncate(28),
        u8.truncate(108),
        u8.truncate(0),
        u8.truncate(6),
        u8.truncate(0),
        u8.truncate(0),
    ]
}

func duplicate_archive(): Vec<u8> {
    return Vec [
        u8.truncate(31),
        u8.truncate(139),
        u8.truncate(8),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(2),
        u8.truncate(255),
        u8.truncate(237),
        u8.truncate(211),
        u8.truncate(49),
        u8.truncate(10),
        u8.truncate(131),
        u8.truncate(64),
        u8.truncate(16),
        u8.truncate(5),
        u8.truncate(208),
        u8.truncate(57),
        u8.truncate(74),
        u8.truncate(78),
        u8.truncate(32),
        u8.truncate(134),
        u8.truncate(136),
        u8.truncate(158),
        u8.truncate(103),
        u8.truncate(139),
        u8.truncate(180),
        u8.truncate(10),
        u8.truncate(113),
        u8.truncate(69),
        u8.truncate(143),
        u8.truncate(239),
        u8.truncate(154),
        u8.truncate(74),
        u8.truncate(236),
        u8.truncate(13),
        u8.truncate(132),
        u8.truncate(125),
        u8.truncate(175),
        u8.truncate(249),
        u8.truncate(195),
        u8.truncate(111),
        u8.truncate(166),
        u8.truncate(250),
        u8.truncate(169),
        u8.truncate(201),
        u8.truncate(91),
        u8.truncate(142),
        u8.truncate(123),
        u8.truncate(181),
        u8.truncate(69),
        u8.truncate(223),
        u8.truncate(117),
        u8.truncate(223),
        u8.truncate(44),
        u8.truncate(174),
        u8.truncate(89),
        u8.truncate(188),
        u8.truncate(78),
        u8.truncate(247),
        u8.truncate(209),
        u8.truncate(247),
        u8.truncate(195),
        u8.truncate(115),
        u8.truncate(136),
        u8.truncate(71),
        u8.truncate(27),
        u8.truncate(63),
        u8.truncate(176),
        u8.truncate(204),
        u8.truncate(57),
        u8.truncate(125),
        u8.truncate(202),
        u8.truncate(203),
        u8.truncate(168),
        u8.truncate(211),
        u8.truncate(52),
        u8.truncate(190),
        u8.truncate(131),
        u8.truncate(122),
        u8.truncate(37),
        u8.truncate(251),
        u8.truncate(175),
        u8.truncate(122),
        u8.truncate(255),
        u8.truncate(121),
        u8.truncate(157),
        u8.truncate(140),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(224),
        u8.truncate(143),
        u8.truncate(237),
        u8.truncate(184),
        u8.truncate(102),
        u8.truncate(234),
        u8.truncate(233),
        u8.truncate(0),
        u8.truncate(40),
        u8.truncate(0),
        u8.truncate(0),
    ]
}

func traversal_archive(): Vec<u8> {
    return Vec [
        u8.truncate(31),
        u8.truncate(139),
        u8.truncate(8),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(2),
        u8.truncate(255),
        u8.truncate(237),
        u8.truncate(205),
        u8.truncate(65),
        u8.truncate(10),
        u8.truncate(194),
        u8.truncate(48),
        u8.truncate(20),
        u8.truncate(4),
        u8.truncate(208),
        u8.truncate(127),
        u8.truncate(20),
        u8.truncate(79),
        u8.truncate(80),
        u8.truncate(163),
        u8.truncate(9),
        u8.truncate(122),
        u8.truncate(158),
        u8.truncate(32),
        u8.truncate(217),
        u8.truncate(75),
        u8.truncate(211),
        u8.truncate(130),
        u8.truncate(199),
        u8.truncate(55),
        u8.truncate(186),
        u8.truncate(18),
        u8.truncate(247),
        u8.truncate(10),
        u8.truncate(165),
        u8.truncate(239),
        u8.truncate(109),
        u8.truncate(102),
        u8.truncate(152),
        u8.truncate(205),
        u8.truncate(76),
        u8.truncate(211),
        u8.truncate(177),
        u8.truncate(245),
        u8.truncate(91),
        u8.truncate(189),
        u8.truncate(183),
        u8.truncate(248),
        u8.truncate(157),
        u8.truncate(52),
        u8.truncate(92),
        u8.truncate(74),
        u8.truncate(121),
        u8.truncate(231),
        u8.truncate(240),
        u8.truncate(157),
        u8.truncate(195),
        u8.truncate(233),
        u8.truncate(163),
        u8.truncate(191),
        u8.truncate(246),
        u8.truncate(107),
        u8.truncate(206),
        u8.truncate(231),
        u8.truncate(56),
        u8.truncate(164),
        u8.truncate(248),
        u8.truncate(131),
        u8.truncate(181),
        u8.truncate(47),
        u8.truncate(117),
        u8.truncate(30),
        u8.truncate(151),
        u8.truncate(177),
        u8.truncate(79),
        u8.truncate(143),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(96),
        u8.truncate(139),
        u8.truncate(158),
        u8.truncate(41),
        u8.truncate(2),
        u8.truncate(244),
        u8.truncate(212),
        u8.truncate(0),
        u8.truncate(40),
        u8.truncate(0),
        u8.truncate(0),
    ]
}

func unsupported_archive(): Vec<u8> {
    return Vec [
        u8.truncate(31),
        u8.truncate(139),
        u8.truncate(8),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(2),
        u8.truncate(255),
        u8.truncate(237),
        u8.truncate(206),
        u8.truncate(49),
        u8.truncate(10),
        u8.truncate(128),
        u8.truncate(48),
        u8.truncate(0),
        u8.truncate(3),
        u8.truncate(192),
        u8.truncate(62),
        u8.truncate(197),
        u8.truncate(39),
        u8.truncate(212),
        u8.truncate(82),
        u8.truncate(252),
        u8.truncate(143),
        u8.truncate(131),
        u8.truncate(136),
        u8.truncate(40),
        u8.truncate(14),
        u8.truncate(90),
        u8.truncate(255),
        u8.truncate(111),
        u8.truncate(113),
        u8.truncate(212),
        u8.truncate(185),
        u8.truncate(131),
        u8.truncate(112),
        u8.truncate(183),
        u8.truncate(36),
        u8.truncate(91),
        u8.truncate(178),
        u8.truncate(45),
        u8.truncate(251),
        u8.truncate(26),
        u8.truncate(26),
        u8.truncate(139),
        u8.truncate(213),
        u8.truncate(144),
        u8.truncate(243),
        u8.truncate(147),
        u8.truncate(213),
        u8.truncate(59),
        u8.truncate(191),
        u8.truncate(189),
        u8.truncate(143),
        u8.truncate(49),
        u8.truncate(229),
        u8.truncate(208),
        u8.truncate(165),
        u8.truncate(50),
        u8.truncate(30),
        u8.truncate(243),
        u8.truncate(84),
        u8.truncate(218),
        u8.truncate(158),
        u8.truncate(187),
        u8.truncate(206),
        u8.truncate(186),
        u8.truncate(82),
        u8.truncate(231),
        u8.truncate(3),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(252),
        u8.truncate(199),
        u8.truncate(13),
        u8.truncate(220),
        u8.truncate(62),
        u8.truncate(72),
        u8.truncate(42),
        u8.truncate(0),
        u8.truncate(40),
        u8.truncate(0),
        u8.truncate(0),
    ]
}

func two_entry_archive(): Vec<u8> {
    return Vec [
        u8.truncate(31),
        u8.truncate(139),
        u8.truncate(8),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(2),
        u8.truncate(255),
        u8.truncate(237),
        u8.truncate(209),
        u8.truncate(203),
        u8.truncate(9),
        u8.truncate(128),
        u8.truncate(48),
        u8.truncate(16),
        u8.truncate(4),
        u8.truncate(208),
        u8.truncate(45),
        u8.truncate(197),
        u8.truncate(18),
        u8.truncate(114),
        u8.truncate(8),
        u8.truncate(73),
        u8.truncate(61),
        u8.truncate(177),
        u8.truncate(4),
        u8.truncate(63),
        u8.truncate(253),
        u8.truncate(27),
        u8.truncate(60),
        u8.truncate(6),
        u8.truncate(241),
        u8.truncate(166),
        u8.truncate(8),
        u8.truncate(190),
        u8.truncate(119),
        u8.truncate(153),
        u8.truncate(189),
        u8.truncate(205),
        u8.truncate(194),
        u8.truncate(180),
        u8.truncate(120),
        u8.truncate(94),
        u8.truncate(234),
        u8.truncate(74),
        u8.truncate(206),
        u8.truncate(103),
        u8.truncate(118),
        u8.truncate(99),
        u8.truncate(94),
        u8.truncate(220),
        u8.truncate(37),
        u8.truncate(213),
        u8.truncate(18),
        u8.truncate(83),
        u8.truncate(122),
        u8.truncate(225),
        u8.truncate(183),
        u8.truncate(216),
        u8.truncate(215),
        u8.truncate(173),
        u8.truncate(45),
        u8.truncate(189),
        u8.truncate(50),
        u8.truncate(254),
        u8.truncate(105),
        u8.truncate(254),
        u8.truncate(234),
        u8.truncate(254),
        u8.truncate(213),
        u8.truncate(254),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(119),
        u8.truncate(14),
        u8.truncate(245),
        u8.truncate(189),
        u8.truncate(237),
        u8.truncate(39),
        u8.truncate(0),
        u8.truncate(40),
        u8.truncate(0),
        u8.truncate(0),
    ]
}

blocking func inspect_bytes(bytes: Vec<u8>, limits: ArchiveLimits): ArchiveReport! {
    let source = FragmentedSource { bytes: move bytes, next: 0 }
    return inspect(move source, limits)?
}

blocking func require_failure(
    bytes: Vec<u8>,
    limits: ArchiveLimits,
    code: &str,
): void! {
    let _unexpected = inspect_bytes(move bytes, limits) catch failure {
        if failure.has_code(code) { return }
        return error.new("archive-inspect.test", "inspection returned the wrong failure")
    }
    return error.new("archive-inspect.test", "invalid archive was accepted")
}

test valid_archive_is_published_only_after_complete_validation {
    let report = inspect_bytes(valid_archive(), ArchiveLimits.standard())?
    let path = report.entry_path(0) otherwise {
        return error.new("archive-inspect.test", "validated path is absent")
    }
    let size = report.entry_size(0) otherwise {
        return error.new("archive-inspect.test", "validated size is absent")
    }
    let directory = report.entry_is_directory(0) otherwise {
        return error.new("archive-inspect.test", "validated kind is absent")
    }
    if report.entry_count() != 1
    || path != "a.txt"
    || size != 6
    || directory {
        return error.new("archive-inspect.test", "validated report changed")
    }

    var damaged = valid_archive()
    damaged[94] = 245
    require_failure(
        move damaged,
        ArchiveLimits.standard(),
        "std.compress.invalid_gzip",
    )?
    var truncated = valid_archive()
    let truncated_len = truncated.len() - 1
    truncated.truncate(truncated_len)
    require_failure(
        move truncated,
        ArchiveLimits.standard(),
        "std.compress.invalid_gzip",
    )?
    return
}

test every_resource_limit_is_enforced_before_report_publication {
    require_failure(
        valid_archive(),
        ArchiveLimits.new(100, 268435456, 4096, 240)?,
        "archive-inspect.input_limit",
    )?
    require_failure(
        valid_archive(),
        ArchiveLimits.new(67108864, 5, 4096, 240)?,
        "archive-inspect.output_limit",
    )?
    require_failure(
        valid_archive(),
        ArchiveLimits.new(67108864, 268435456, 4096, 4)?,
        "archive-inspect.path_limit",
    )?
    require_failure(
        two_entry_archive(),
        ArchiveLimits.new(67108864, 268435456, 1, 240)?,
        "archive-inspect.entry_limit",
    )?
    let empty = inspect_bytes(
        empty_archive(),
        ArchiveLimits.new(67108864, 1024, 4096, 240)?,
    )?
    if empty.entry_count() != 0 {
        return error.new("archive-inspect.test", "empty archive published an entry")
    }
    require_failure(
        archive_with_trailing_decoded_bytes(),
        ArchiveLimits.new(67108864, 1024, 4096, 240)?,
        "archive-inspect.output_limit",
    )?
    return
}

test unsafe_and_ambiguous_entries_are_rejected {
    require_failure(
        duplicate_archive(),
        ArchiveLimits.standard(),
        "archive-inspect.duplicate_path",
    )?
    require_failure(
        traversal_archive(),
        ArchiveLimits.standard(),
        "std.archive.invalid_tar",
    )?
    require_failure(
        unsupported_archive(),
        ArchiveLimits.standard(),
        "archive-inspect.unsupported_entry",
    )?
    return
}