1 /*******************************************************************************
2  * Main library import for dproto
3  *
4  * Provides accessors for D string and D structs from proto files/data
5  *
6  * Authors: Matthew Soucy, msoucy@csh.rit.edu
7  * Date: Apr 1, 2015
8  * Version: 0.0.3
9  */
10 module dproto.unittests;
11 
12 import dproto.dproto;
13 
14 unittest
15 {
16 	assert(__traits(compiles, ProtocolBufferFromString!"message Test
17 		{
18 			optional string verySimple = 1;
19 		}"));
20 }
21 
22 unittest
23 {
24 	assert(__traits(compiles, ProtocolBufferFromString!"
25 		message Test
26 		{
27 			optional string verySimple = 1;
28 			enum TestEnum
29 			{
30 				ONE = 1;
31 				UNO = 1;
32 				TOW = 2;
33 			}
34 		}"));
35 }
36 
37 unittest
38 {
39 	mixin ProtocolBufferFromString!"
40 		message Test
41 		{
42 			required int32 id = 1;
43 			optional string verySimple = 2;
44 			enum TestEnum
45 			{
46 				ONE = 1;
47 				UNO = 1;
48 				TOW = 2;
49 			}
50 			optional TestEnum testValue = 3;
51 		}";
52 }
53 
54 unittest
55 {
56 	assert(__traits(compiles, ProtocolBufferFromString!"message Test
57 		{
58 			optional string verySimple = 1;
59 			enum TestEnum
60 			{
61 				ONE = 1;
62 				UNO = 1;
63 				TOW = 2;
64 			}
65 
66 			optional string testValue = 2;
67 		}"));
68 }
69 
70 unittest
71 {
72 	assert(__traits(compiles, ProtocolBufferFromString!"
73 	message Test
74 	{
75 		optional string verySimple = 1;
76 		message NestedTest
77 		{
78 			optional string verySimple = 1;
79 		}
80 
81 		optional NestedTest value = 2;
82 	}"));
83 }
84 
85 unittest
86 {
87 	assert(__traits(compiles, ProtocolBufferFromString!"
88 	message Test
89 	{
90 		optional string verySimple = 1;
91 		message NestedTest
92 		{
93 			optional string verySimple2 = 1;
94 		}
95 
96 		optional NestedTest value = 2;
97 	}"));
98 }
99 
100 unittest
101 {
102 	assert(__traits(compiles, ProtocolBufferFromString!"
103 	message Test
104 	{
105 		optional string verySimple = 1;
106 		message NestedTest
107 		{
108 			optional string verySimple = 1;
109 		}
110 
111 		repeated NestedTest value = 2;
112 	}"));
113 }
114 
115 unittest
116 {
117 	assert(__traits(compiles, ProtocolBufferFromString!"
118 	message Test
119 	{
120 		required int32 id = 3;
121 		optional string verySimple = 1;
122 		message NestedTest
123 		{
124 			required string verySimple = 1;
125 		}
126 
127 		required NestedTest value = 2;
128 	}"));
129 }
130 
131 unittest
132 {
133 	assert(__traits(compiles, ProtocolBufferFromString!"
134 	message Test
135 	{
136 		required int32 id = 3;
137 		optional string verySimple = 1;
138 		message NestedTest
139 		{
140 			required string verySimple = 1;
141 		}
142 
143 		repeated NestedTest value = 2;
144 	}"));
145 }
146 
147 unittest
148 {
149 	assert(__traits(compiles, ProtocolBufferFromString!"
150 	message Test
151 	{
152 		required int32 id = 3;
153 		optional string verySimple = 1;
154 		message NestedTest
155 		{
156 			required string verySimple = 1;
157 		}
158 
159 		optional NestedTest value = 2;
160 	}"));
161 }
162 
163 unittest
164 {
165 	assert(__traits(compiles, ProtocolBufferFromString!"
166 	message Test
167 	{
168 		required int32 id = 3;
169 		optional string verySimple = 1;
170 		message NestedTest
171 		{
172 			required string verySimple = 1;
173 		}
174 
175 		repeated NestedTest value = 2;
176 	}"));
177 }
178 
179 unittest
180 {
181 	enum serviceDefinition = "
182 	message ServiceRequest {
183 		string request = 1;
184 	}
185 	message ServiceResponse {
186 		string response = 1;
187 	}
188 	service TestService {
189 		rpc TestMethod (ServiceRequest) returns (ServiceResponse);
190 	}
191 	";
192 
193 	// Force code coverage in doveralls
194 	import std.string;
195 	import std.format;
196 	import dproto.parse;
197 
198 	auto normalizedServiceDefinition = "%3.3p".format(ParseProtoSchema("<none>", serviceDefinition));
199 
200 	assert(__traits(compiles, ProtocolBufferFromString!serviceDefinition));
201 	assert(__traits(compiles, ProtocolBufferInterface!serviceDefinition));
202 	assert(__traits(compiles, ProtocolBufferRpc!serviceDefinition));
203 	assert(__traits(compiles, ProtocolBufferImpl!serviceDefinition));
204 	assert(__traits(compiles, ProtocolBufferStruct!serviceDefinition));
205 
206 	// Example from README.md.
207 	mixin ProtocolBufferInterface!serviceDefinition;
208 
209 	class ServiceImplementation : TestService
210 	{
211 		ServiceResponse TestMethod(ServiceRequest input)
212 		{
213 			ServiceResponse output;
214 			output.response = "received: " ~ input.request;
215 			return output;
216 		}
217 	}
218 
219 	auto serviceTest = new ServiceImplementation;
220 	ServiceRequest input;
221 	input.request = "message";
222 	assert(serviceTest.TestMethod(input).response == "received: message");
223 
224 }
225 
226 unittest
227 {
228 	mixin ProtocolBufferFromString!"
229 	enum PhoneType {
230 		MOBILE = 0;
231 		HOME = 0;
232 		WORK = 2;
233 	}
234 
235 	message Person {
236 		required string name = 1;
237 		required int32 id = 2;
238 		optional string email = 3;
239 
240 		message PhoneNumber {
241 			required string number = 1;
242 			optional PhoneType type = 2 [default = HOME];
243 		}
244 
245 		repeated PhoneNumber phone = 4;
246 	}
247 	";
248 
249 	Person t;
250 	assert(t.name == "");
251 	assert(t.id == 0);
252 	assert(t.phone.length == 0);
253 	version (Have_painlessjson)
254 	{
255 		assert(t.toJson() == `{"email":"","id":0,"name":"","phone":[]}`);
256 	}
257 
258 	t.name = "Max Musterman";
259 	assert(t.name == "Max Musterman");
260 
261 	t.id = 3;
262 	assert(t.id == 3);
263 
264 	t.email = "Max.Musterman@example.com";
265 	assert(t.email);
266 	assert(t.email == "Max.Musterman@example.com");
267 
268 	Person.PhoneNumber pn1;
269 	pn1.number = "0123456789";
270 	assert(pn1.number == "0123456789");
271 	assert(pn1.type == PhoneType.HOME);
272 	assert(pn1.type == PhoneType.MOBILE);
273 
274 	pn1.type = PhoneType.WORK;
275 	assert(pn1.type == PhoneType.WORK);
276 	assert(pn1.type);
277 	assert(pn1.type == 2);
278 
279 	t.phone ~= pn1;
280 	assert(t.phone[0] == pn1);
281 	assert(t.phone.length == 1);
282 
283 	version (Have_painlessjson)
284 	{
285 		assert(
286 			t.toJson() == `{"email":"Max.Musterman@example.com","id":3,"name":"Max Musterman","phone":[{"number":"0123456789","type":2}]}`);
287 	}
288 
289 	pn1.type = pn1.type.init;
290 	assert(pn1.type == PhoneType.HOME);
291 
292 	t.phone = t.phone.init;
293 	assert(t.phone.length == 0);
294 
295 	t.email = t.email.init;
296 	assert(t.email == "");
297 }
298 
299 unittest
300 {
301 	mixin ProtocolBufferFromString!"
302 	message Person {
303 		required string name = 1;
304 		required int32 id = 2;
305 		optional string email = 3;
306 
307 		enum PhoneType {
308 			MOBILE = 0;
309 			HOME = 0;
310 			WORK = 2;
311 		}
312 
313 		message PhoneNumber {
314 			required string number = 1;
315 			optional PhoneType type = 2 [default = HOME];
316 		}
317 
318 		repeated PhoneNumber phone = 4;
319 	}
320 
321 	message AddressBook {
322 		repeated Person person = 1;
323 	}
324 	";
325 
326 	Person t;
327 	assert(t.name == "");
328 	assert(t.id == 0);
329 	assert(t.phone.length == 0);
330 
331 	t.name = "Max Musterman";
332 	assert(t.name == "Max Musterman");
333 
334 	t.id = 3;
335 	assert(t.id == 3);
336 
337 	t.email = "Max.Musterman@example.com";
338 	assert(t.email);
339 	assert(t.email == "Max.Musterman@example.com");
340 
341 	Person.PhoneNumber pn1;
342 	pn1.number = "0123456789";
343 	assert(pn1.number == "0123456789");
344 
345 	t.phone ~= pn1;
346 	assert(t.phone[0] == pn1);
347 	assert(t.phone.length == 1);
348 
349 	t.phone = t.phone.init;
350 	assert(t.phone.length == 0);
351 
352 	t.email = t.email.init;
353 	assert(t.email == "");
354 
355 	AddressBook addressbook;
356 	assert(addressbook.person.length == 0);
357 	addressbook.person ~= t;
358 	addressbook.person ~= t;
359 	assert(addressbook.person[0] == t);
360 	assert(addressbook.person[0] == addressbook.person[1]);
361 	assert(addressbook.person.length == 2);
362 }
363 
364 unittest
365 {
366 	mixin ProtocolBufferFromString!"
367 	enum PhoneType {
368 		MOBILE = 0;
369 		HOME = 0;
370 		WORK = 2;
371 	}
372 
373 	message Person {
374 		required string name = 1;
375 		required int32 id = 2;
376 		optional string email = 3;
377 
378 		message PhoneNumber {
379 			required string number = 1;
380 			optional PhoneType type = 2 [default = HOME];
381 		}
382 
383 		repeated PhoneNumber phone = 4;
384 	}
385 
386 	message AddressBook {
387 		repeated Person person = 1;
388 	}
389 	";
390 
391 	Person t;
392 	t.name = "Max Musterman";
393 	t.id = 3;
394 	t.email = "test@example.com";
395 
396 	Person.PhoneNumber pn1;
397 	pn1.number = "0123456789";
398 	pn1.type = PhoneType.WORK;
399 
400 	Person.PhoneNumber pn2;
401 	pn2.number = "0123456789";
402 
403 	t.phone = [pn1, pn2];
404 	AddressBook addressbook;
405 	addressbook.person ~= t;
406 	addressbook.person ~= t;
407 
408 	ubyte[] serializedObject = addressbook.serialize();
409 
410 	AddressBook addressbook2 = AddressBook.fromProto(serializedObject);
411 	assert(addressbook2.person.length == 2);
412 	foreach (t2; addressbook2.person[0 .. 1])
413 	{
414 		assert(t2.name == "Max Musterman");
415 		assert(t2.id == 3);
416 		assert(t2.email);
417 		assert(t2.email == "test@example.com");
418 		assert(t2.phone[0].number == "0123456789");
419 		assert(t2.phone[0].type == PhoneType.WORK);
420 		assert(t2.phone[1].number == "0123456789");
421 		assert(t2.phone[1].type == PhoneType.HOME);
422 		assert(t2.phone[1].type == PhoneType.MOBILE);
423 		assert(t2.phone.length == 2);
424 	}
425 	//the gdc-4.8 evaluates false here. Maybe an compiler bug.
426 	version (DigitalMars)
427 	{
428 		assert(addressbook2.person[0] == addressbook.person[1]);
429 	}
430 }
431 
432 unittest
433 {
434 	mixin ProtocolBufferFromString!"
435 	message Person {
436 		required string name = 1;
437 		required int32 id = 2;
438 		optional string email = 3;
439 
440 		enum PhoneType {
441 			MOBILE = 0;
442 			HOME = 0;
443 			WORK = 2;
444 		}
445 
446 		message PhoneNumber {
447 			required string number = 1;
448 			optional PhoneType type = 2 [default = HOME];
449 		}
450 
451 		repeated PhoneNumber phone = 4;
452 	}
453 
454 	message AddressBook {
455 		repeated Person person = 1;
456 	}
457 	";
458 
459 	Person t;
460 	assert(t.name == "");
461 	assert(t.id == 0);
462 	assert(t.phone.length == 0);
463 
464 	t.name = "Max Musterman";
465 	assert(t.name == "Max Musterman");
466 
467 	t.id = 3;
468 	assert(t.id == 3);
469 
470 	t.email = "Max.Musterman@example.com";
471 	assert(t.email);
472 	assert(t.email == "Max.Musterman@example.com");
473 
474 	Person.PhoneNumber pn1;
475 	pn1.number = "0123456789";
476 	assert(pn1.number == "0123456789");
477 	assert(pn1.type == Person.PhoneType.HOME);
478 	assert(pn1.type == Person.PhoneType.MOBILE);
479 
480 	pn1.type = Person.PhoneType.WORK;
481 	assert(pn1.type == Person.PhoneType.WORK);
482 	assert(pn1.type == 2);
483 	assert(pn1.type);
484 
485 	t.phone ~= pn1;
486 	assert(t.phone[0] == pn1);
487 	assert(t.phone.length == 1);
488 
489 	pn1.type = pn1.type.init;
490 	assert(pn1.type == Person.PhoneType.HOME);
491 
492 	t.phone = t.phone.init;
493 	assert(t.phone.length == 0);
494 
495 	t.email = t.email.init;
496 	assert(t.email == "");
497 
498 	AddressBook addressbook;
499 	assert(addressbook.person.length == 0);
500 	addressbook.person ~= t;
501 	addressbook.person ~= t;
502 	assert(addressbook.person[0] == t);
503 	assert(addressbook.person[0] == addressbook.person[1]);
504 	assert(addressbook.person.length == 2);
505 
506 	static struct OutBuf
507 	{
508 	@nogc:
509 	@safe:
510 		void put(in ubyte)
511 		{
512 		}
513 
514 		void put(in ubyte[])
515 		{
516 		}
517 	}
518 
519 	@nogc void testNoGC()
520 	{
521 		OutBuf buf;
522 		addressbook.serializeTo(buf);
523 	}
524 
525 	testNoGC();
526 }
527 
528 unittest
529 {
530 	mixin ProtocolBufferFromString!"
531 	message Person {
532 		required string name = 1;
533 	}
534 	";
535 
536 	static auto rvalue(in ubyte[] val)
537 	{
538 		return val;
539 	}
540 
541 	enum data = cast(ubyte[])[1 << 3 | 2, "abc".length] ~ cast(ubyte[]) "abc";
542 	const(ubyte)[] val = data;
543 	assert(val.length == 5);
544 	assert(Person(rvalue(val)).name == "abc");
545 	assert(val.length == 5);
546 	assert(Person(val).name == "abc");
547 	assert(val.length == 0);
548 	Person p;
549 	val = data;
550 	assert(val.length == 5);
551 	p.deserialize(rvalue(val));
552 	assert(val.length == 5);
553 	assert(p.name == "abc");
554 	p.name = null;
555 	p.deserialize(val);
556 	assert(val.length == 0);
557 	assert(p.name == "abc");
558 }
559 
560 unittest
561 {
562 	mixin ProtocolBufferFromString!"
563 	message Field_Name_Equals_Internal_Variable_Name {
564 		required int32 r = 1;
565 		required int32 data = 2;
566 		required int32 msgdata = 3;
567 	}
568 	";
569 }
570 
571 unittest
572 {
573 	import dproto.exception;
574 	import dproto.serialize;
575 	import dproto.parse;
576 	import std.string : strip;
577 
578 	auto proto_src = `import "foo/baz.proto";`;
579 	auto proto_struct = ParseProtoSchema("<none>", proto_src);
580 	auto d_src = proto_struct.toD;
581 	assert(`mixin ProtocolBuffer!"foo/baz.proto";` == d_src,
582 		"Mixin string should not have two double quotes " ~ d_src);
583 	assert(proto_src == proto_struct.toProto.strip,
584 		"Round tripping to protobuf source should yield starting text " ~ proto_struct.toProto);
585 }
586 
587 unittest
588 {
589 	mixin ProtocolBufferFromString!`
590 	enum RecordFlags
591 	{
592 		Announce = 1;
593 		Cancel = 2;
594 		SomeAnotherFlag = 4; // look at the enumeration!
595 	}
596 	message KeyValue
597 	{
598 		required bytes key = 1;
599 		optional RecordFlags flags = 2;
600 		optional bytes payload = 3;
601 	}
602 	message ECDSASignature
603 	{
604 		required bytes signature = 1;
605 		required bytes pubKey = 2;
606 	}
607 	message Signed
608 	{
609 		required ECDSASignature esignature = 1;
610 		required KeyValue keyValue = 2;
611 	}`;
612 
613 	Signed d1;
614 	d1.keyValue.key = cast(ubyte[]) "key data";
615 	d1.keyValue.payload = cast(ubyte[]) "value data";
616 	auto ser = d1.serialize();
617 	Signed d2 = ser;
618 	assert(d1.keyValue.key == d2.keyValue.key);
619 	assert(d1.keyValue.payload == d2.keyValue.payload);
620 }
621 
622 unittest
623 {
624 
625 	mixin ProtocolBufferFromString!`
626 		message DNSPayload
627 		{
628 			repeated bytes assignOwnerPubKeys = 1;
629 			repeated bytes assignManagersPubKeys = 2;
630 
631 			repeated bytes ns = 3;
632 		}
633 	`;
634 
635 	DNSPayload p1;
636 	p1.ns ~= [1, 2, 3];
637 	auto buf = p1.serialize();
638 
639 	DNSPayload p2;
640 	p2.deserialize(buf);
641 	assert(p1 == p2);
642 }
643 
644 unittest
645 {
646 	mixin ProtocolBufferFromString!"
647 		message Person {
648 			required uint32 id = 1;
649 		}";
650 }
651 
652 unittest
653 {
654 	mixin ProtocolBufferFromString!`
655 		message TestStructure
656 		{
657 			optional string optional_string = 1;
658 			required string required_string = 2;
659 			repeated string repeated_string = 3;
660 		}
661 	`;
662 	import dproto.attributes : TagId;
663 
664 	assert(TagId!(TestStructure.optional_string) == 1);
665 	assert(TagId!(TestStructure.required_string) == 2);
666 	assert(TagId!(TestStructure.repeated_string) == 3);
667 }
668 
669 unittest
670 {
671 	mixin ProtocolBufferFromString!"
672         message Stats {
673             optional int32 agility = 1;
674             optional int32 stamina = 2;
675         }
676         message Character {
677             optional string name = 1;
678             optional Stats stats = 2;
679         }
680         message Account {
681             optional string owner = 1;
682             optional Character main = 2;
683         }
684     ";
685 	const int agility = 200;
686 	auto acct = Account();
687 	auto main = Character();
688 	main.name = "Hogan";
689 	main.stats = Stats();
690 	main.stats.agility = agility;
691 	acct.main = main;
692 	auto ser = acct.serialize();
693 	Account acct_rx;
694 	acct_rx.deserialize(ser);
695 	import std.string : format;
696 
697 	assert(acct_rx.main.stats.agility == agility, format("Expected %d, got %d",
698 		agility, acct_rx.main.stats.agility));
699 
700 }
701 
702 unittest
703 {
704 	enum pbstring = q{
705 		enum Enum {
706 			A = 0;
707 			B = 1;
708 			C = 2;
709 		}
710 
711 		message Msg {
712 			optional Enum unset = 1;
713 			optional Enum isset_first = 2 [default = A];
714 			optional Enum isset_last = 3 [default = C];
715 			required Enum unset_required = 4;
716 			required Enum isset_required = 5 [default = B];
717 			optional int32 i1 = 6 [default = 42];
718 			optional int32 i2 = 7;
719 			required int32 i3 = 8 [default = 24];
720 			required int32 i4 = 9;
721 		}
722 	};
723 
724 	// Force code coverage in doveralls
725 	import std.string;
726 	import std.format;
727 	import dproto.parse;
728 
729 	auto normalizedServiceDefinition = "%3.3p".format(ParseProtoSchema("<none>", pbstring));
730 
731 	mixin ProtocolBufferFromString!pbstring;
732 
733 	Msg msg;
734 	assert(msg.unset == Enum.A);
735 	assert(msg.isset_first == Enum.A);
736 	assert(msg.isset_last == Enum.C);
737 	assert(msg.unset_required == Enum.A);
738 	assert(msg.isset_required == Enum.B);
739 	assert(msg.i1 == 42);
740 	assert(msg.i2 == typeof(msg.i2).init);
741 	assert(msg.i3 == 24);
742 	assert(msg.i4 == typeof(msg.i4).init);
743 }
744 
745 unittest
746 {
747 	import dproto.parse;
748 	import dproto.exception;
749 	import std.exception;
750 
751 	enum pbstring = q{
752 message Info {
753    optional int32 version = 1 [default = -1];
754 }
755 	};
756 	assertThrown!DProtoReservedWordException(ParseProtoSchema(
757 				"<none>",
758 				`option dproto_reserved_fmt = "%s"; ` ~ pbstring));
759 	assertNotThrown!DProtoReservedWordException(ParseProtoSchema(
760 				"<none>",
761 				`option dproto_reserved_fmt = "%s_"; ` ~ pbstring));
762 	assertNotThrown!DProtoReservedWordException(ParseProtoSchema(
763 				"<none>", pbstring));
764 }
765 
766 unittest
767 {
768 	mixin ProtocolBufferFromString!`
769 message HeaderBBox {
770     required sint64 left = 1;
771     required sint64 right = 2;
772     required sint64 top = 3;
773     required sint64 bottom = 4;
774 }`;
775 	HeaderBBox headerBBox;
776 
777 	headerBBox.left = 10;
778 	headerBBox.right = 5;
779 	headerBBox.top = -32;
780 	headerBBox.bottom = -24;
781 
782 	auto hbb = headerBBox.serialize();
783 	headerBBox = HeaderBBox(hbb); // Error occurred here
784 
785 	assert(headerBBox.left == 10);
786 	assert(headerBBox.right == 5);
787 	assert(headerBBox.top == -32);
788 	assert(headerBBox.bottom == -24);
789 }
790 
791 unittest
792 {
793 	assert(!__traits(compiles, mixin(`mixin ProtocolBufferFromString!q{
794     message One {
795         required string a;
796         required int32 b;
797     }
798 };`)),
799 		"Malformed proto structure accepted");
800 }
801 
802 unittest
803 {
804 	import std.algorithm;
805 
806 	mixin ProtocolBufferFromString!`
807 	message Foo {
808 		repeated uint32 arr = 1 [packed=true];
809 	}
810 `;
811 
812 	Foo foo;
813 	foo.arr = [1];
814 
815 	auto serialized_foo = foo.serialize();
816 
817 	auto foo2 = Foo(serialized_foo);
818 
819 	assert(equal(foo.arr, foo2.arr));
820 }
821 
822 unittest
823 {
824 	// Issue #86
825 	import dproto.parse;
826 	import dproto.exception;
827 	enum pbstring = q{
828     message ReservedWordTest {
829         required bool notReservedWord = 1;
830     }
831 };
832 	mixin ProtocolBufferFromString!pbstring;
833 	assert(ParseProtoSchema("<none>", pbstring).toD());
834 }
835 
836 unittest
837 {
838 	import std.algorithm;
839 
840 	mixin ProtocolBufferFromString!`
841 	message FooA {
842 		repeated uint32 arr = 1 [packed=true];
843 	}
844 	message FooB {
845 		repeated uint32 arr = 1;
846 	}
847 `;
848 
849 	FooA foo;
850 	foo.arr = [1, 3, 5, 7, 2, 4, 6, 8];
851 
852 	auto serialized_foo = foo.serialize();
853 	auto foo2 = FooB(serialized_foo);
854 	assert(equal(foo.arr, foo2.arr));
855 	auto foo3 = FooA(foo2.serialize());
856 	assert(equal(foo2.arr, foo3.arr));
857 }
858 
859 unittest
860 {
861 	// Issue #86
862 	import dproto.parse;
863 	import dproto.exception;
864 	import std.exception;
865 
866 	enum pbstring = q{
867     message ReservedWordTest {
868         required bool notReservedWord;
869     }
870 };
871 	assertThrown!DProtoSyntaxException(ParseProtoSchema("<none>", pbstring));
872 }
873 
874 unittest
875 {
876 	// Issue #89
877 	import dproto.dproto;
878 
879 	mixin ProtocolBufferFromString!q{
880 		message Test {
881 			repeated double id = 1 [packed = true];
882 		}
883 
884 	};
885 
886     Test t;
887 
888     t.id = [123];
889 
890     auto s = t.serialize();
891     t = Test(s);
892 
893     assert(t.id == [123]);
894 }
895 
896 unittest
897 {
898     // Issue #92
899     import dproto.dproto : ProtocolBufferFromString;
900     import dproto.parse : ParseProtoSchema;
901 
902     enum syntaxProto2 = `
903         syntax = "proto2";
904     `;
905 	static assert(__traits(compiles, ProtocolBufferFromString!syntaxProto2));
906 
907     enum schemaProto2 = ParseProtoSchema("<none>", syntaxProto2);
908     static assert(schemaProto2.syntax == `"proto2"`);
909 
910     enum syntaxProto3 = `
911         syntax = "proto3";
912     `;
913 	static assert(__traits(compiles, ProtocolBufferFromString!syntaxProto3));
914 
915     enum schemaProto3 = ParseProtoSchema("<none>", syntaxProto3);
916     static assert(schemaProto3.syntax == `"proto3"`);
917 
918     enum syntaxNoEquals = `
919         syntax "proto2";
920     `;
921 	static assert(!__traits(compiles, ProtocolBufferFromString!syntaxNoEquals));
922 
923     enum syntaxNoQuotes = `
924         syntax = proto2;
925     `;
926 	static assert(!__traits(compiles, ProtocolBufferFromString!syntaxNoQuotes));
927 
928     enum syntaxNoLQuote = `
929         syntax = proto2";
930     `;
931 	static assert(!__traits(compiles, ProtocolBufferFromString!syntaxNoLQuote));
932 
933     enum syntaxNoRQuote = `
934         syntax = "proto2;
935     `;
936 	static assert(!__traits(compiles, ProtocolBufferFromString!syntaxNoRQuote));
937 
938     enum syntaxNoSemicolon = `
939         syntax = "proto2"
940     `;
941 	static assert(!__traits(compiles, ProtocolBufferFromString!syntaxNoSemicolon));
942 }
943 
944 unittest
945 {
946 	// Issue #26
947 	import dproto.parse;
948 	import dproto.exception;
949 	import std.exception;
950 
951 	enum pbstring = q{
952 	import public;
953 };
954 	mixin ProtocolBufferFromString!pbstring;
955 }
956 
957 unittest
958 {
959 	// Issue #26
960 	import dproto.parse;
961 	import dproto.exception;
962 	import std.exception;
963 
964 	enum pbstring = q{
965 	import public "proto/example.proto";
966 };
967 	assert(ParseProtoSchema("<none>", pbstring).toD());
968 }
969 
970 unittest
971 {
972 	import dproto.parse;
973 	import dproto.exception;
974 	import std.exception;
975 
976 	enum pbstring = q{
977 	enum Foo {
978 		option allow_alias = false;
979 		ONE = 1;
980 		TWO = 1;
981 		THREE = 3;
982 	}
983 };
984 	assertThrown!DProtoSyntaxException(ParseProtoSchema("<none>", pbstring));
985 
986 	enum pbstring2 = q{
987 	enum Foo {
988 		ONE = 1;
989 		TWO = 1;
990 		THREE = 3;
991 	}
992 };
993 	assertNotThrown!DProtoSyntaxException(ParseProtoSchema("<none>", pbstring2));
994 }
995 
996 unittest
997 {
998 	// Issue #92
999 
1000 	import dproto.parse;
1001 	import dproto.exception;
1002 	import std.exception;
1003 
1004 	enum pbstring = `
1005 		syntax = "proto3";
1006 	`;
1007 	assertNotThrown!DProtoSyntaxException(ParseProtoSchema("<none>", pbstring));
1008 }
1009