├── .dir-locals.el ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── dub.sdl ├── dub.selections.json ├── reggaefile.d ├── source └── nogc │ ├── conv.d │ ├── exception.d │ └── package.d └── tests ├── test_main.d └── ut ├── conv.d ├── exception.d ├── issues.d └── package.d /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((d-mode 5 | (fldd-dub-configuration . "unittest"))) 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Dub Test 7 | strategy: 8 | matrix: 9 | os: [ubuntu-20.04, windows-latest] 10 | dc: [dmd-2.097.0, ldc-1.26.0] 11 | 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Install D compiler 17 | uses: dlang-community/setup-dlang@v1.0.5 18 | with: 19 | compiler: ${{ matrix.dc }} 20 | 21 | - name: Run tests 22 | run: dub test -q --build=unittest-cov 23 | 24 | - name: Build binary 25 | run: dub build -q 26 | 27 | - uses: codecov/codecov-action@v1 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | .reggae 3 | bin 4 | bin.ldc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Atila Neves 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nogc 2 | 3 | [![CI](https://github.com/atilaneves/nogc/actions/workflows/ci.yml/badge.svg)](https://github.com/atilaneves/nogc/actions/workflows/ci.yml) 4 | [![Coverage](https://codecov.io/gh/atilaneves/nogc/branch/master/graph/badge.svg)](https://codecov.io/gh/atilaneves/nogc) 5 | 6 | 7 | Utilities to write `@nogc` code, including converting values to strings (`text`) 8 | and a variant of `std.exception.enforce` that is `@nogc` but limits the type 9 | of the exception thrown to be `NoGcException`. Examples: 10 | 11 | ```d 12 | // text is @system because it returns a slice to a static array 13 | // if you need to store the string you'll need to make a copy 14 | // since consecutive calls will return the same slice and it will 15 | // be mutated 16 | @nogc @system unittest { 17 | import nogc.conv: text; 18 | // works with basic types and user defined structs/classes 19 | assert(text(1, " ", "foo", " ", 2.0) == "1 foo 2.000000"); 20 | } 21 | 22 | 23 | // enforce is @safe, since it internally makes a call to `text` but 24 | // immediately throws an exception, and casting it to `string` makes 25 | // it immutable. Ugly but it works. 26 | @nogc @safe unittest { 27 | import nogc.exception: enforce; 28 | import nogc.conv: text; 29 | const expected = 1; 30 | const actual = 1; 31 | enforce(actual == expected, "Expected: ", expected, " but got: ", actual); 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "nogc" 2 | description "Utilities to write @nogc code" 3 | authors "Atila Neves" 4 | copyright "Copyright © 2017-2018 Atila Neves" 5 | license "BSD 3-clause" 6 | 7 | targetType "library" 8 | targetPath "bin" 9 | 10 | dflags "-dip1008" 11 | 12 | dependency "automem" version="~>0.6.0" 13 | dependency "localimport" version="*" 14 | 15 | 16 | configuration "library" { 17 | } 18 | 19 | 20 | configuration "unittest" { 21 | targetType "executable" 22 | targetName "ut" 23 | 24 | importPaths "tests" 25 | sourcePaths "tests" 26 | mainSourceFile "tests/test_main.d" 27 | 28 | dflags "-dip25" "-dip1000" 29 | 30 | dependency "unit-threaded" version="*" 31 | dependency "test_allocator" version="*" 32 | } 33 | 34 | 35 | configuration "asan" { 36 | targetType "executable" 37 | targetName "asan" 38 | 39 | importPaths "tests" 40 | sourcePaths "tests" 41 | mainSourceFile "tests/test_main.d" 42 | 43 | dflags "-dip25" "-dip1000" 44 | dflags "-fsanitize=address" platform="ldc" 45 | 46 | versions "unitUnthreaded" "unitThreadedLight" 47 | 48 | dependency "unit-threaded" version="*" 49 | dependency "test_allocator" version="*" 50 | } 51 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "automem": "0.6.1", 5 | "localimport": "1.3.0", 6 | "test_allocator": "0.3.2", 7 | "unit-threaded": "0.9.3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /reggaefile.d: -------------------------------------------------------------------------------- 1 | import reggae; 2 | enum commonFlags = "-w -g -debug"; 3 | 4 | alias lib = dubDefaultTarget!(CompilerFlags(commonFlags)); 5 | alias ut = dubTestTarget!(CompilerFlags(commonFlags)); 6 | alias asan = dubConfigurationTarget!( 7 | Configuration("asan"), 8 | CompilerFlags(commonFlags ~ " -unittest -cov -fsanitize=address"), 9 | LinkerFlags("-fsanitize=address"), 10 | ); 11 | 12 | mixin build!(ut, optional!lib, optional!asan); 13 | -------------------------------------------------------------------------------- /source/nogc/conv.d: -------------------------------------------------------------------------------- 1 | /** 2 | Analogous to std.conv but @nogc 3 | */ 4 | 5 | module nogc.conv; 6 | 7 | 8 | import localimport; 9 | import std.experimental.allocator.mallocator: Mallocator; 10 | 11 | 12 | enum BUFFER_SIZE = 1024; 13 | 14 | @nogc: 15 | 16 | auto text(size_t bufferSize = BUFFER_SIZE, Allocator = Mallocator, Args...) 17 | (scope auto ref Args args) 18 | @safe 19 | { 20 | import automem.vector: StringA; 21 | import std.traits: Unqual; 22 | import core.stdc.stdio: snprintf; 23 | 24 | alias String = StringA!Allocator; 25 | 26 | scope char[bufferSize] buffer = void; 27 | String ret; 28 | 29 | foreach(ref arg; args) { 30 | auto ptr = &buffer[0]; 31 | auto len = buffer.length; 32 | auto fmt = format(arg); 33 | // @trusted due to appends in `value` 34 | auto rawVal = () @trusted { return value!Allocator(arg); }(); 35 | 36 | static if(is(Unqual!(typeof(rawVal)): StringA!Allocator)) 37 | // @trusted because stringz is @system 38 | scope val = () @trusted { return rawVal.stringz; }(); 39 | else 40 | alias val = rawVal; 41 | 42 | const index = () @trusted { return snprintf(ptr, len, fmt, val); }(); 43 | auto toAppend = index >= buffer.length - 1 44 | ? buffer[0 .. $ - 1] 45 | : buffer[0 .. index]; 46 | 47 | () @trusted { ret ~= toAppend; }(); 48 | } 49 | 50 | return ret; 51 | } 52 | 53 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == int) || is(T == short) || is(T == byte)) { 54 | return &"%d"[0]; 55 | } 56 | 57 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == uint) || is(T == ushort) || is(T == ubyte)) { 58 | return &"%u"[0]; 59 | } 60 | 61 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == long)) { 62 | return &"%ld"[0]; 63 | } 64 | 65 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == ulong)) { 66 | return &"%lu"[0]; 67 | } 68 | 69 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == char)) { 70 | return &"%c"[0]; 71 | } 72 | 73 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == float)) { 74 | return &"%f"[0]; 75 | } 76 | 77 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == double)) { 78 | return &"%lf"[0]; 79 | } 80 | 81 | private const(char)* format(T)(auto ref const(T) arg) if(from.std.traits.isPointer!T) { 82 | return &"%p"[0]; 83 | } 84 | 85 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == string)) { 86 | return &"%s"[0]; 87 | } 88 | 89 | private const(char)* format(T)(auto ref const(T) arg) if(is(T == void[])) { 90 | return &"%s"[0]; 91 | } 92 | 93 | private const(char)* format(T)(auto ref const(T) arg) 94 | if(is(T == enum) || is(T == bool) || (from.std.range.primitives.isInputRange!T && !is(T == string)) || 95 | from.std.traits.isAssociativeArray!T || from.std.traits.isAggregateType!T) 96 | { 97 | return &"%s"[0]; 98 | } 99 | 100 | 101 | private const(char)* format(T)(auto ref const(T) arg) 102 | if(is(T == const(void)[])) 103 | { 104 | return &"%s"[0]; 105 | } 106 | 107 | 108 | private auto value(Allocator = Mallocator, T)(auto ref const(T) arg) 109 | if((from.std.traits.isScalarType!T || from.std.traits.isPointer!T) && !is(T == enum) && !is(T == bool)) 110 | { 111 | return arg; 112 | } 113 | 114 | private auto value(Allocator = Mallocator, T)(auto ref const(T) arg) if(is(T == enum)) { 115 | import std.traits: EnumMembers; 116 | import std.conv: to; 117 | 118 | final switch(arg) { 119 | static foreach(member; EnumMembers!T) { 120 | case member: 121 | mixin(`return &"` ~ member.to!string ~ `"[0];`); 122 | } 123 | } 124 | } 125 | 126 | 127 | private auto value(Allocator = Mallocator, T)(auto ref const(T) arg) if(is(T == bool)) { 128 | return arg 129 | ? &"true"[0] 130 | : &"false"[0]; 131 | } 132 | 133 | 134 | private auto value(Allocator = Mallocator, T)(auto ref const(T) arg) if(is(T == string)) { 135 | import automem.vector: StringA; 136 | return StringA!Allocator(arg); 137 | } 138 | 139 | private auto value(Allocator = Mallocator, T)(T arg) if(from.std.range.primitives.isInputRange!(from.std.traits.Unqual!T) && !is(T == string)) { 140 | 141 | import automem.vector: StringA; 142 | import std.range: hasLength, isForwardRange, walkLength, save; 143 | 144 | StringA!Allocator ret; 145 | 146 | ret ~= "["; 147 | 148 | static if(hasLength!T) 149 | const length = arg.length; 150 | else static if(isForwardRange!T) 151 | const length = arg.save.walkLength; 152 | else 153 | const length = size_t.max; 154 | 155 | size_t i; 156 | foreach(elt; arg) { 157 | ret ~= text!(BUFFER_SIZE, Allocator)(elt)[]; 158 | if(++i < length) ret ~= ", "; 159 | } 160 | 161 | ret ~= "]"; 162 | 163 | return ret; 164 | } 165 | 166 | 167 | private auto value(Allocator = Mallocator, T)(auto ref const(T) arg) if(from.std.traits.isAssociativeArray!T) { 168 | 169 | import automem.vector: StringA; 170 | 171 | StringA!Allocator ret; 172 | 173 | ret ~= "["; 174 | 175 | size_t i; 176 | foreach(key, val; arg) { 177 | ret ~= text!(BUFFER_SIZE, Allocator)(key)[]; 178 | ret ~= ": "; 179 | ret ~= text!(BUFFER_SIZE, Allocator)(val)[]; 180 | if(++i < arg.length) ret ~= ", "; 181 | } 182 | 183 | ret ~= "]"; 184 | 185 | return ret; 186 | } 187 | 188 | private auto value(Allocator = Mallocator, T)(auto ref const(T) arg) 189 | if(from.std.traits.isAggregateType!T && !from.std.range.primitives.isInputRange!T) 190 | { 191 | import automem.vector: StringA; 192 | 193 | StringA!Allocator ret; 194 | 195 | ret ~= T.stringof; 196 | ret ~= "("; 197 | 198 | foreach(i, elt; arg.tupleof) { 199 | ret ~= text!(BUFFER_SIZE, Allocator)(elt)[]; 200 | if(i != arg.tupleof.length - 1) ret ~= ", "; 201 | } 202 | 203 | ret ~= ")"; 204 | 205 | return ret; 206 | } 207 | 208 | 209 | private auto value(Allocator = Mallocator, T)(auto ref const(T) arg) 210 | if(is(T == void[])) 211 | { 212 | return &"[void]"[0]; 213 | } 214 | 215 | private auto value(Allocator = Mallocator, T)(T arg) @trusted if(is(T == const(void)[])) { 216 | return &"[void]"[0]; 217 | } 218 | 219 | 220 | auto toWStringz(Allocator = Mallocator, T)(in T str) { 221 | import automem.vector: Vector; 222 | import std.utf: byUTF; 223 | import std.traits: isSomeString; 224 | 225 | Vector!(immutable(wchar), Allocator) ret; 226 | () @trusted { ret.reserve(str.length * str[0].sizeof + 1); }(); 227 | 228 | static if(isSomeString!T) 229 | alias range = str; 230 | else static if(__traits(compiles, str.range)) 231 | auto range = str.range; 232 | else static if(__traits(compiles, str[])) 233 | auto range = str[]; 234 | else 235 | static assert(false, "Don't know how to iterate by wchar on `" ~ T.stringof ~ "`"); 236 | 237 | foreach(ch; range.byUTF!wchar) 238 | () @trusted { ret ~= ch; }(); 239 | 240 | // null terminator 241 | () @trusted { ret ~= 0; }(); 242 | 243 | return ret; 244 | } 245 | -------------------------------------------------------------------------------- /source/nogc/exception.d: -------------------------------------------------------------------------------- 1 | /** 2 | This module implements utility code to throw exceptions in @nogc code. 3 | */ 4 | module nogc.exception; 5 | 6 | 7 | import std.experimental.allocator.mallocator: Mallocator; 8 | 9 | 10 | T enforce(E = NoGcException, T, Args...) 11 | (T value, auto ref Args args, in string file = __FILE__, in size_t line = __LINE__) 12 | if (is(typeof({ if (!value) {} }))) 13 | { 14 | import std.functional: forward; 15 | if(!value) NoGcException.throw_(File(file), Line(line), forward!args); 16 | return value; 17 | } 18 | 19 | alias NoGcException = NoGcExceptionImpl!Mallocator; 20 | 21 | class NoGcExceptionImpl(A): Exception { 22 | 23 | import automem.traits: isGlobal; 24 | import automem.vector: StringA; 25 | import std.meta: anySatisfy; 26 | 27 | alias Allocator = A; 28 | 29 | // just to let enforce pass the right arguments to the constructor 30 | protected static struct Dummy {} 31 | protected enum isDummy(T) = is(T == Dummy); 32 | 33 | private StringA!Allocator _msg; 34 | static if(!isGlobal!Allocator) private Allocator _allocator; 35 | 36 | this(Args...) 37 | (auto ref Args args, string file = __FILE__, size_t line = __LINE__) 38 | if(isGlobal!Allocator && !anySatisfy!(isDummy, Args)) 39 | { 40 | import std.functional: forward; 41 | this(Dummy(), file, line, forward!args); 42 | } 43 | 44 | /// 45 | @("exception can be constructed in @nogc code") 46 | @safe @nogc pure unittest { 47 | static const exception = new NoGcException(); 48 | } 49 | 50 | this(Args...) 51 | (Allocator allocator, auto ref Args args, string file = __FILE__, size_t line = __LINE__) 52 | if(!anySatisfy!(isDummy, Args)) 53 | { 54 | import std.functional: forward; 55 | this(Dummy(), file, line, forward!args); 56 | this._allocator = allocator; 57 | } 58 | 59 | // exists to be used from throw_ 60 | protected this(Args...) 61 | (in Dummy _, in string file, in size_t line, scope auto ref Args args) 62 | if(isGlobal!Allocator) 63 | { 64 | import nogc.conv: text, BUFFER_SIZE; 65 | import std.functional: forward; 66 | 67 | _msg = text!(BUFFER_SIZE, A)(forward!args); 68 | // Setting `Exception.msg` to the allocated memory in this class would 69 | // mean it could escape DIP1000 checks. The only sane alternative is 70 | // setting it to null 71 | super(null, file, line); 72 | } 73 | 74 | /** 75 | Throws a new NoGcException allowing to adjust the file name and line number 76 | */ 77 | static void throw_(T = typeof(this), Args...)(in File file, in Line line, scope auto ref Args args) { 78 | import std.functional: forward; 79 | throw new T(Dummy(), file.value, line.value, forward!args); 80 | } 81 | 82 | /// Manually free the msg 83 | final void free() @safe @nogc scope { 84 | _msg.free; 85 | } 86 | 87 | /** 88 | We can't let client code access `Exception.msg` since it's not scoped 89 | with DIP1000. 90 | */ 91 | auto msg() @safe @nogc return scope { 92 | return _msg.range; 93 | } 94 | } 95 | 96 | struct File { string value; } 97 | struct Line { size_t value; } 98 | 99 | 100 | mixin template NoGcExceptionCtors() { 101 | 102 | import nogc.exception: File, Line; 103 | import automem.traits: isGlobal; 104 | import std.meta: anySatisfy; 105 | 106 | this(Args...) 107 | (auto ref Args args, string file = __FILE__, size_t line = __LINE__) 108 | if(isGlobal!Allocator && !anySatisfy!(isDummy, Args)) 109 | { 110 | import std.functional: forward; 111 | super(forward!args, file, line); 112 | } 113 | 114 | this(Args...) 115 | (Allocator allocator, auto ref Args args, string file = __FILE__, size_t line = __LINE__) 116 | if(!anySatisfy!(isDummy, Args)) 117 | { 118 | import std.functional: forward; 119 | super(allocator, forward!args, file, line); 120 | } 121 | 122 | 123 | // exists to be used from throw_ 124 | protected this(Args...) 125 | (in Dummy _, in string file, in size_t line, scope auto ref Args args) 126 | if(isGlobal!Allocator) 127 | { 128 | import std.functional: forward; 129 | super(_, file, line, forward!args); 130 | } 131 | 132 | /** 133 | Throws a new NoGcException allowing to adjust the file name and line number 134 | */ 135 | static void throw_(T = typeof(this), Args...)(in File file, in Line line, scope auto ref Args args) { 136 | import std.functional: forward; 137 | typeof(super).throw_!T(file, line, forward!args); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /source/nogc/package.d: -------------------------------------------------------------------------------- 1 | module nogc; 2 | 3 | 4 | public import nogc.conv; 5 | public import nogc.exception; 6 | 7 | 8 | version(unitThreadedLight): 9 | 10 | // text is @system because it returns a slice to a static array 11 | // if you need to store the string you'll need to make a copy 12 | // since consecutive calls will return the same slice and it will 13 | // be mutated 14 | @nogc @system unittest { 15 | import nogc.conv: text; 16 | // works with basic types and user defined structs/classes 17 | assert(text(1, " ", "foo", " ", 2.0)[] == "1 foo 2.000000"); 18 | } 19 | 20 | 21 | // enforce is @safe, since it internally makes a call to `text` but 22 | // immediately throws an exception, and casting it to `string` makes 23 | // it immutable. Ugly but it works. 24 | @nogc @safe unittest { 25 | import nogc.exception: enforce; 26 | import nogc.conv: text; 27 | const expected = 1; 28 | const actual = 1; 29 | enforce(actual == expected, "Expected: ", expected, " but got: ", actual); 30 | } 31 | -------------------------------------------------------------------------------- /tests/test_main.d: -------------------------------------------------------------------------------- 1 | import unit_threaded; 2 | mixin runTestsMain!( 3 | "nogc.conv", 4 | "nogc.exception", 5 | 6 | "ut.conv", 7 | "ut.exception", 8 | "ut.issues", 9 | ); 10 | -------------------------------------------------------------------------------- /tests/ut/conv.d: -------------------------------------------------------------------------------- 1 | module ut.conv; 2 | 3 | import ut; 4 | import nogc.conv; 5 | 6 | 7 | @("text with multiple arguments") 8 | @safe unittest { 9 | const actual = () @safe @nogc nothrow { return text(1, " ", 2.0, " ", true); }(); 10 | actual.range.shouldEqual("1 2.000000 true"); 11 | } 12 | 13 | @("text with void[]") 14 | @safe unittest { 15 | void[] arg; 16 | const actual = () @nogc nothrow { return text(arg); }(); 17 | actual.range.shouldEqual("[void]"); 18 | } 19 | 20 | @("toWStringz") 21 | @safe unittest { 22 | import std.conv: to; 23 | 24 | const str = "pokémon"; 25 | const exp = "pokémon".to!wstring; 26 | 27 | const act = () @nogc nothrow { return str.toWStringz(); }(); 28 | const slice = () @trusted @nogc { return act[0 .. 7]; }(); 29 | slice.shouldEqual("pokémon".to!wstring); 30 | } 31 | 32 | @("text with at limit characters") 33 | @safe unittest { 34 | const actual = () @nogc nothrow { return text!4("foo"); }(); 35 | actual.range.shouldEqual("foo"); 36 | } 37 | 38 | @("text with 1 fewer char than needed") 39 | @safe unittest { 40 | const actual = () @nogc nothrow { return text!3("foo"); }(); 41 | actual.range.shouldEqual("fo"); 42 | } 43 | 44 | version(unitThreadedLight) {} 45 | else { 46 | @("text.bool") 47 | @safe unittest { 48 | text(false).range.should == "false"; 49 | text(true).range.should == "true"; 50 | } 51 | } 52 | 53 | @("text.enum") 54 | @safe @nogc unittest { 55 | import std.algorithm: equal; 56 | enum Enum { foo, bar, baz } 57 | const actual = Enum.bar.text; 58 | assert(equal(actual.range, "bar")); 59 | } 60 | 61 | @("text.struct") 62 | @safe @nogc unittest { 63 | static struct Struct { 64 | int i; 65 | double d; 66 | } 67 | 68 | const actual = Struct(2, 33.3).text; 69 | debug actual.range.shouldEqual("Struct(2, 33.300000)"); 70 | } 71 | 72 | @("text.string") 73 | @safe @nogc unittest { 74 | const actual = "foobar".text; 75 | debug actual.range.shouldEqual("foobar"); 76 | } 77 | 78 | 79 | @("text.inputrange") 80 | @safe @nogc unittest { 81 | import std.range: only; 82 | const actual = only(0, 1, 2, 3).text; 83 | debug actual.range.shouldEqual("[0, 1, 2, 3]"); 84 | } 85 | 86 | @("text.aa") 87 | @safe unittest { 88 | const aa = ["foo": 1, "bar": 2]; 89 | const actual = () @nogc { return aa.text; }(); 90 | try 91 | debug actual.range.shouldEqual(`[foo: 1, bar: 2]`); 92 | catch(UnitTestException _) 93 | debug actual.range.shouldEqual(`[bar: 2, foo: 1]`); 94 | } 95 | 96 | 97 | @("toWtringz") 98 | @safe unittest { 99 | const wstr = "foobar".toWStringz; 100 | wstr.range.shouldEqual("foobar"w ~ 0); 101 | } 102 | 103 | 104 | @("text.toWtringz") 105 | @safe unittest { 106 | const wstr = text("foo ", 42, " bar").toWStringz; 107 | wstr.range.shouldEqual("foo 42 bar"w ~ 0); 108 | } 109 | -------------------------------------------------------------------------------- /tests/ut/exception.d: -------------------------------------------------------------------------------- 1 | module ut.exception; 2 | 3 | 4 | import ut; 5 | import nogc.exception; 6 | 7 | 8 | @("enforce.derived") 9 | @safe unittest { 10 | import std.array: array; 11 | 12 | string msg, file; 13 | size_t line, expectedLine; 14 | () @safe { 15 | try { 16 | expectedLine = __LINE__ + 1; 17 | () @nogc { enforce(false, "foo", 5, "bar"); }(); 18 | } catch(NoGcException ex) { 19 | msg = ex.msg.array; 20 | file = ex.file.idup; 21 | line = ex.line; 22 | } 23 | }(); 24 | 25 | msg.should == "foo5bar"; 26 | file.should == __FILE__; 27 | line.should == expectedLine; 28 | } 29 | 30 | 31 | @("enforce.base") 32 | @safe unittest { 33 | import std.array: array; 34 | 35 | string msg, file; 36 | size_t line, expectedLine; 37 | () @safe { 38 | try { 39 | expectedLine = __LINE__ + 1; 40 | () @nogc { enforce(false, "foo", 5, "bar"); }(); 41 | } catch(Exception ex) { 42 | // not copying here on purpose - the compiler can't help 43 | msg = ex.msg; 44 | file = ex.file; 45 | line = ex.line; 46 | } 47 | }(); 48 | 49 | // no way of getting msg from the base class 50 | msg.should == ""; 51 | file.should == __FILE__; 52 | line.should == expectedLine; 53 | } 54 | 55 | 56 | version(FIXME) { 57 | @("TestAllocator") 58 | @safe @nogc unittest { 59 | 60 | import test_allocator; 61 | static TestAllocator allocator; 62 | 63 | alias MyException = NoGcExceptionImpl!(TestAllocator*); 64 | 65 | // just to make sure it compiles 66 | void func() { 67 | throw new MyException(&allocator, 42, " foobar ", 33.3); 68 | } 69 | } 70 | } 71 | 72 | 73 | @("malloc") 74 | @safe @nogc unittest { 75 | 76 | import std.algorithm: equal; 77 | 78 | try 79 | throw new NoGcException(42, " foobar ", 33.3); 80 | catch(NoGcException e) { 81 | assert(equal(e.msg, "42 foobar 33.300000"), "msg not what expected"); 82 | assert(e.file == __FILE__); 83 | assert(e.line == __LINE__ - 4); 84 | } 85 | } 86 | 87 | 88 | @("derived exception") 89 | @safe @nogc unittest { 90 | 91 | static class MyException: NoGcException { 92 | 93 | static int numInstances; 94 | 95 | this(A...)(auto ref A args) { 96 | import std.functional: forward; 97 | super(forward!args); 98 | ++numInstances; 99 | } 100 | 101 | ~this() { 102 | --numInstances; 103 | } 104 | } 105 | 106 | try { 107 | assert(MyException.numInstances == 0); 108 | throw new MyException(42, " foobar ", 33.3); 109 | } catch(MyException e) { 110 | assert(MyException.numInstances == 1); 111 | } 112 | 113 | static if(__VERSION__ > 2086) 114 | assert(MyException.numInstances == 0); 115 | } 116 | 117 | 118 | @("throw.base") 119 | @safe @nogc unittest { 120 | try 121 | NoGcException.throw_(File("foo.d"), Line(42), 122 | "this is the message ", 33, " or ", 77.7, " hah"); 123 | catch(Exception e) { 124 | assert(e.line == 42); 125 | debug { 126 | e.file.should == "foo.d"; 127 | e.line.should == 42; 128 | // msg not available via Exception 129 | e.msg.should == ""; 130 | } 131 | return; 132 | } 133 | 134 | assert(false, "Didn't catch the exception"); 135 | } 136 | 137 | 138 | @("throw.child") 139 | @safe @nogc unittest { 140 | 141 | import std.array: array; 142 | 143 | static class MyException: NoGcException { 144 | mixin NoGcExceptionCtors; 145 | } 146 | 147 | try 148 | MyException.throw_(File("foo.d"), Line(42), 149 | "this is the message ", 33, " or ", 77.7, " hah"); 150 | catch(MyException e) { 151 | assert(e.line == 42); 152 | debug { 153 | e.file.should == "foo.d"; 154 | e.line.should == 42; 155 | e.msg.array.should == "this is the message 33 or 77.700000 hah"; 156 | } 157 | return; 158 | } 159 | 160 | assert(false, "Didn't catch the exception"); 161 | } 162 | -------------------------------------------------------------------------------- /tests/ut/issues.d: -------------------------------------------------------------------------------- 1 | module ut.issues; 2 | 3 | import ut; 4 | import nogc; 5 | 6 | 7 | @("4") 8 | @safe @nogc unittest { 9 | import core.stdc.stdio: puts; 10 | 11 | static struct S1 { 12 | int i = 42; 13 | } 14 | 15 | static struct Z { 16 | char* stringz() const @nogc @system { 17 | assert(0); 18 | } 19 | } 20 | 21 | static struct UnsafeAllocator { 22 | 23 | import std.experimental.allocator.mallocator: Mallocator; 24 | enum instance = UnsafeAllocator.init; 25 | 26 | void deallocate(void[] bytes) @nogc @system { 27 | Mallocator.instance.deallocate(bytes); 28 | } 29 | void[] allocate(size_t sz) @nogc @system { 30 | return Mallocator.instance.allocate(sz); 31 | } 32 | } 33 | 34 | S1 a; 35 | Z* z; 36 | auto t = text!(BUFFER_SIZE, UnsafeAllocator)(a, z); 37 | } 38 | -------------------------------------------------------------------------------- /tests/ut/package.d: -------------------------------------------------------------------------------- 1 | module ut; 2 | 3 | public import unit_threaded; 4 | --------------------------------------------------------------------------------