1 // Copyright 2019 The Amber Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or parseried.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "gtest/gtest.h"
16 #include "src/amberscript/parser.h"
17 
18 namespace amber {
19 namespace amberscript {
20 
21 using AmberScriptParserTest = testing::Test;
22 
TEST_F(AmberScriptParserTest,Pipeline)23 TEST_F(AmberScriptParserTest, Pipeline) {
24   std::string in = R"(
25 SHADER vertex my_shader PASSTHROUGH
26 SHADER fragment my_fragment GLSL
27 # GLSL Shader
28 END
29 
30 PIPELINE graphics my_pipeline
31   ATTACH my_shader
32   ATTACH my_fragment
33 END
34 )";
35 
36   Parser parser;
37   Result r = parser.Parse(in);
38   ASSERT_TRUE(r.IsSuccess()) << r.Error();
39 
40   auto script = parser.GetScript();
41   EXPECT_EQ(2U, script->GetShaders().size());
42 
43   const auto& pipelines = script->GetPipelines();
44   ASSERT_EQ(1U, pipelines.size());
45 
46   const auto* pipeline = pipelines[0].get();
47   EXPECT_EQ("my_pipeline", pipeline->GetName());
48   EXPECT_EQ(PipelineType::kGraphics, pipeline->GetType());
49 
50   const auto& shaders = pipeline->GetShaders();
51   ASSERT_EQ(2U, shaders.size());
52 
53   ASSERT_TRUE(shaders[0].GetShader() != nullptr);
54   EXPECT_EQ("my_shader", shaders[0].GetShader()->GetName());
55   EXPECT_EQ(kShaderTypeVertex, shaders[0].GetShader()->GetType());
56   EXPECT_EQ(static_cast<uint32_t>(0),
57             shaders[0].GetShaderOptimizations().size());
58 
59   ASSERT_TRUE(shaders[1].GetShader() != nullptr);
60   EXPECT_EQ("my_fragment", shaders[1].GetShader()->GetName());
61   EXPECT_EQ(kShaderTypeFragment, shaders[1].GetShader()->GetType());
62   EXPECT_EQ(static_cast<uint32_t>(0),
63             shaders[1].GetShaderOptimizations().size());
64 
65   EXPECT_EQ(pipelines[0]->GetPipelineData()->GetPatchControlPoints(), 3u);
66 }
67 
TEST_F(AmberScriptParserTest,PipelineMissingEnd)68 TEST_F(AmberScriptParserTest, PipelineMissingEnd) {
69   std::string in = R"(
70 SHADER vertex my_shader PASSTHROUGH
71 PIPELINE graphics my_pipeline
72   ATTACH my_shader
73 )";
74 
75   Parser parser;
76   Result r = parser.Parse(in);
77   ASSERT_FALSE(r.IsSuccess());
78   EXPECT_EQ("5: PIPELINE missing END command", r.Error());
79 }
80 
TEST_F(AmberScriptParserTest,PipelineWithExtraParams)81 TEST_F(AmberScriptParserTest, PipelineWithExtraParams) {
82   std::string in = R"(
83 PIPELINE graphics my_pipeline INVALID
84   ATTACH my_shader
85 END
86 )";
87 
88   Parser parser;
89   Result r = parser.Parse(in);
90   ASSERT_FALSE(r.IsSuccess());
91   EXPECT_EQ("2: extra parameters after PIPELINE command: INVALID", r.Error());
92 }
93 
TEST_F(AmberScriptParserTest,PipelineInvalidType)94 TEST_F(AmberScriptParserTest, PipelineInvalidType) {
95   std::string in = "PIPELINE my_name\nEND";
96 
97   Parser parser;
98   Result r = parser.Parse(in);
99   ASSERT_FALSE(r.IsSuccess());
100   EXPECT_EQ("1: unknown pipeline type: my_name", r.Error());
101 }
102 
TEST_F(AmberScriptParserTest,PipelineMissingName)103 TEST_F(AmberScriptParserTest, PipelineMissingName) {
104   std::string in = "PIPELINE compute\nEND";
105 
106   Parser parser;
107   Result r = parser.Parse(in);
108   ASSERT_FALSE(r.IsSuccess());
109   EXPECT_EQ("2: invalid token when looking for pipeline name", r.Error());
110 }
111 
TEST_F(AmberScriptParserTest,PipelineWithInvalidTokenType)112 TEST_F(AmberScriptParserTest, PipelineWithInvalidTokenType) {
113   std::string in = "PIPELINE 123 my_pipeline\nEND";
114 
115   Parser parser;
116   Result r = parser.Parse(in);
117   ASSERT_FALSE(r.IsSuccess());
118   EXPECT_EQ("1: invalid token when looking for pipeline type", r.Error());
119 }
120 
TEST_F(AmberScriptParserTest,PipelineWithInvalidTokenName)121 TEST_F(AmberScriptParserTest, PipelineWithInvalidTokenName) {
122   std::string in = "PIPELINE compute 123\nEND";
123 
124   Parser parser;
125   Result r = parser.Parse(in);
126   ASSERT_FALSE(r.IsSuccess());
127   EXPECT_EQ("1: invalid token when looking for pipeline name", r.Error());
128 }
129 
TEST_F(AmberScriptParserTest,PipelineEmpty)130 TEST_F(AmberScriptParserTest, PipelineEmpty) {
131   std::string in = "PIPELINE compute my_pipeline\nEND";
132 
133   Parser parser;
134   Result r = parser.Parse(in);
135   ASSERT_FALSE(r.IsSuccess());
136   EXPECT_EQ("compute pipeline requires a compute shader", r.Error());
137 }
138 
TEST_F(AmberScriptParserTest,PipelineWithUnknownCommand)139 TEST_F(AmberScriptParserTest, PipelineWithUnknownCommand) {
140   std::string in = R"(
141 PIPELINE compute my_pipeline
142   SHADER vertex my_shader PASSTHROUGH
143 END)";
144 
145   Parser parser;
146   Result r = parser.Parse(in);
147   ASSERT_FALSE(r.IsSuccess());
148   EXPECT_EQ("3: unknown token in pipeline block: SHADER", r.Error());
149 }
150 
TEST_F(AmberScriptParserTest,DuplicatePipelineName)151 TEST_F(AmberScriptParserTest, DuplicatePipelineName) {
152   std::string in = R"(
153 SHADER vertex my_shader PASSTHROUGH
154 SHADER fragment my_fragment GLSL
155 # Fragment shader
156 END
157 
158 PIPELINE graphics my_pipeline
159   ATTACH my_shader
160   ATTACH my_fragment
161 END
162 PIPELINE graphics my_pipeline
163   ATTACH my_shader
164   ATTACH my_fragment
165 END)";
166 
167   Parser parser;
168   Result r = parser.Parse(in);
169   ASSERT_FALSE(r.IsSuccess());
170   EXPECT_EQ("14: duplicate pipeline name provided", r.Error());
171 }
172 
TEST_F(AmberScriptParserTest,PipelineDefaultColorBuffer)173 TEST_F(AmberScriptParserTest, PipelineDefaultColorBuffer) {
174   std::string in = R"(
175 SHADER vertex my_shader PASSTHROUGH
176 SHADER fragment my_fragment GLSL
177 # GLSL Shader
178 END
179 
180 PIPELINE graphics my_pipeline
181   ATTACH my_shader
182   ATTACH my_fragment
183 END
184 PIPELINE graphics my_pipeline2
185   ATTACH my_shader
186   ATTACH my_fragment
187 END)";
188 
189   Parser parser;
190   Result r = parser.Parse(in);
191   ASSERT_TRUE(r.IsSuccess()) << r.Error();
192 
193   auto script = parser.GetScript();
194   const auto& pipelines = script->GetPipelines();
195   ASSERT_EQ(2U, pipelines.size());
196 
197   ASSERT_EQ(1U, pipelines[0]->GetColorAttachments().size());
198   const auto& buf1 = pipelines[0]->GetColorAttachments()[0];
199   ASSERT_TRUE(buf1.buffer != nullptr);
200 
201   Buffer* buffer1 = buf1.buffer;
202   EXPECT_EQ(FormatType::kB8G8R8A8_UNORM, buffer1->GetFormat()->GetFormatType());
203   EXPECT_EQ(0u, buf1.location);
204   EXPECT_EQ(250u * 250u, buffer1->ElementCount());
205   EXPECT_EQ(250u * 250u * 4u, buffer1->ValueCount());
206   EXPECT_EQ(250u * 250u * 4u * sizeof(uint8_t), buffer1->GetSizeInBytes());
207 
208   ASSERT_EQ(1U, pipelines[1]->GetColorAttachments().size());
209   const auto& buf2 = pipelines[1]->GetColorAttachments()[0];
210   ASSERT_TRUE(buf2.buffer != nullptr);
211   ASSERT_EQ(buffer1, buf2.buffer);
212   EXPECT_EQ(0u, buf2.location);
213   EXPECT_EQ(FormatType::kB8G8R8A8_UNORM,
214             buf2.buffer->GetFormat()->GetFormatType());
215   EXPECT_EQ(250u * 250u, buf2.buffer->ElementCount());
216   EXPECT_EQ(250u * 250u * 4u, buf2.buffer->ValueCount());
217   EXPECT_EQ(250u * 250u * 4u * sizeof(uint8_t), buf2.buffer->GetSizeInBytes());
218 }
219 
TEST_F(AmberScriptParserTest,PipelineDefaultColorBufferMismatchSize)220 TEST_F(AmberScriptParserTest, PipelineDefaultColorBufferMismatchSize) {
221   std::string in = R"(
222 SHADER vertex my_shader PASSTHROUGH
223 SHADER fragment my_fragment GLSL
224 # GLSL Shader
225 END
226 
227 PIPELINE graphics my_pipeline
228   ATTACH my_shader
229   ATTACH my_fragment
230 END
231 PIPELINE graphics my_pipeline2
232   ATTACH my_shader
233   ATTACH my_fragment
234   FRAMEBUFFER_SIZE 256 256
235 END)";
236 
237   Parser parser;
238   Result r = parser.Parse(in);
239   ASSERT_FALSE(r.IsSuccess());
240 
241   EXPECT_EQ("shared framebuffer must have same size over all PIPELINES",
242             r.Error());
243 }
244 
TEST_F(AmberScriptParserTest,PipelinePolygonMode)245 TEST_F(AmberScriptParserTest, PipelinePolygonMode) {
246   std::string in = R"(
247 SHADER vertex my_shader PASSTHROUGH
248 SHADER fragment my_fragment GLSL
249 # GLSL Shader
250 END
251 
252 PIPELINE graphics my_pipeline_default
253   ATTACH my_shader
254   ATTACH my_fragment
255   FRAMEBUFFER_SIZE 256 256
256 END
257 PIPELINE graphics my_pipeline_fill
258   ATTACH my_shader
259   ATTACH my_fragment
260   POLYGON_MODE fill
261   FRAMEBUFFER_SIZE 256 256
262 END
263 PIPELINE graphics my_pipeline_line
264   ATTACH my_shader
265   ATTACH my_fragment
266   POLYGON_MODE line
267   FRAMEBUFFER_SIZE 256 256
268 END
269 PIPELINE graphics my_pipeline_point
270   ATTACH my_shader
271   ATTACH my_fragment
272   POLYGON_MODE point
273   FRAMEBUFFER_SIZE 256 256
274 END)";
275 
276   Parser parser;
277   Result r = parser.Parse(in);
278   ASSERT_TRUE(r.IsSuccess());
279 
280   auto script = parser.GetScript();
281   const auto& pipelines = script->GetPipelines();
282   ASSERT_EQ(4U, pipelines.size());
283 
284   auto mode0 = pipelines[0]->GetPipelineData()->GetPolygonMode();
285   ASSERT_EQ(mode0, PolygonMode::kFill);
286   auto mode1 = pipelines[1]->GetPipelineData()->GetPolygonMode();
287   ASSERT_EQ(mode1, PolygonMode::kFill);
288   auto mode2 = pipelines[2]->GetPipelineData()->GetPolygonMode();
289   ASSERT_EQ(mode2, PolygonMode::kLine);
290   auto mode3 = pipelines[3]->GetPipelineData()->GetPolygonMode();
291   ASSERT_EQ(mode3, PolygonMode::kPoint);
292 }
293 
TEST_F(AmberScriptParserTest,PipelineMissingPolygonMode)294 TEST_F(AmberScriptParserTest, PipelineMissingPolygonMode) {
295   std::string in = R"(
296 SHADER vertex my_shader PASSTHROUGH
297 SHADER fragment my_fragment GLSL
298 # GLSL Shader
299 END
300 
301 PIPELINE graphics my_pipeline
302   ATTACH my_shader
303   ATTACH my_fragment
304   POLYGON_MODE
305   FRAMEBUFFER_SIZE 256 256
306 END)";
307 
308   Parser parser;
309   Result r = parser.Parse(in);
310   ASSERT_FALSE(r.IsSuccess());
311 
312   EXPECT_EQ("11: missing mode in POLYGON_MODE command", r.Error());
313 }
314 
TEST_F(AmberScriptParserTest,PipelineInvalidPolygonMode)315 TEST_F(AmberScriptParserTest, PipelineInvalidPolygonMode) {
316   std::string in = R"(
317 SHADER vertex my_shader PASSTHROUGH
318 SHADER fragment my_fragment GLSL
319 # GLSL Shader
320 END
321 
322 PIPELINE graphics my_pipeline
323   ATTACH my_shader
324   ATTACH my_fragment
325   POLYGON_MODE foo
326   FRAMEBUFFER_SIZE 256 256
327 END)";
328 
329   Parser parser;
330   Result r = parser.Parse(in);
331   ASSERT_FALSE(r.IsSuccess());
332 
333   EXPECT_EQ("10: invalid polygon mode: foo", r.Error());
334 }
335 
TEST_F(AmberScriptParserTest,DerivePipeline)336 TEST_F(AmberScriptParserTest, DerivePipeline) {
337   std::string in = R"(
338 SHADER vertex my_shader PASSTHROUGH
339 SHADER fragment my_fragment GLSL
340 # GLSL Shader
341 END
342 SHADER fragment other_fragment GLSL
343 # GLSL Shader
344 END
345 BUFFER buf1 DATA_TYPE int32 SIZE 20 FILL 5
346 BUFFER buf2 DATA_TYPE int32 SIZE 20 FILL 7
347 
348 PIPELINE graphics parent_pipeline
349   ATTACH my_shader
350   ATTACH my_fragment
351   BIND BUFFER buf1 AS storage DESCRIPTOR_SET 1 BINDING 3
352 END
353 
354 DERIVE_PIPELINE child_pipeline FROM parent_pipeline
355   ATTACH other_fragment
356   BIND BUFFER buf2 AS storage DESCRIPTOR_SET 1 BINDING 3
357 END
358 )";
359 
360   Parser parser;
361   Result r = parser.Parse(in);
362   ASSERT_TRUE(r.IsSuccess()) << r.Error();
363 
364   auto script = parser.GetScript();
365   const auto& pipelines = script->GetPipelines();
366   ASSERT_EQ(2U, pipelines.size());
367 
368   const auto* pipeline1 = pipelines[0].get();
369   auto buffers1 = pipeline1->GetBuffers();
370   ASSERT_EQ(1U, buffers1.size());
371   EXPECT_EQ("buf1", buffers1[0].buffer->GetName());
372   EXPECT_EQ(1u, buffers1[0].descriptor_set);
373   EXPECT_EQ(3u, buffers1[0].binding);
374 
375   auto shaders1 = pipeline1->GetShaders();
376   ASSERT_EQ(2U, shaders1.size());
377   EXPECT_EQ("my_shader", shaders1[0].GetShader()->GetName());
378   EXPECT_EQ("my_fragment", shaders1[1].GetShader()->GetName());
379 
380   const auto* pipeline2 = pipelines[1].get();
381   EXPECT_EQ("child_pipeline", pipeline2->GetName());
382 
383   auto buffers2 = pipeline2->GetBuffers();
384   ASSERT_EQ(1U, buffers2.size());
385   EXPECT_EQ("buf2", buffers2[0].buffer->GetName());
386   EXPECT_EQ(1u, buffers2[0].descriptor_set);
387   EXPECT_EQ(3u, buffers2[0].binding);
388 
389   auto shaders2 = pipeline2->GetShaders();
390   ASSERT_EQ(2U, shaders2.size());
391   EXPECT_EQ("my_shader", shaders2[0].GetShader()->GetName());
392   EXPECT_EQ("other_fragment", shaders2[1].GetShader()->GetName());
393 }
394 
TEST_F(AmberScriptParserTest,DerivePipelineMissingEnd)395 TEST_F(AmberScriptParserTest, DerivePipelineMissingEnd) {
396   std::string in = R"(
397 SHADER vertex my_shader PASSTHROUGH
398 SHADER fragment my_fragment GLSL
399 # GLSL Shader
400 END
401 
402 PIPELINE graphics parent_pipeline
403   ATTACH my_shader
404   ATTACH my_fragment
405 END
406 
407 DERIVE_PIPELINE derived_pipeline FROM parent_pipeline
408 )";
409 
410   Parser parser;
411   Result r = parser.Parse(in);
412   ASSERT_FALSE(r.IsSuccess());
413   EXPECT_EQ("13: DERIVE_PIPELINE missing END command", r.Error());
414 }
415 
TEST_F(AmberScriptParserTest,DerivePipelineMissingPipelineName)416 TEST_F(AmberScriptParserTest, DerivePipelineMissingPipelineName) {
417   std::string in = R"(
418 SHADER vertex my_shader PASSTHROUGH
419 SHADER fragment my_fragment GLSL
420 # GLSL Shader
421 END
422 
423 PIPELINE graphics parent_pipeline
424   ATTACH my_shader
425   ATTACH my_fragment
426 END
427 
428 DERIVE_PIPELINE FROM parent_pipeline
429 END
430 )";
431 
432   Parser parser;
433   Result r = parser.Parse(in);
434   ASSERT_FALSE(r.IsSuccess());
435   EXPECT_EQ("12: missing pipeline name for DERIVE_PIPELINE command", r.Error());
436 }
437 
TEST_F(AmberScriptParserTest,DerivePipelineMissingFrom)438 TEST_F(AmberScriptParserTest, DerivePipelineMissingFrom) {
439   std::string in = R"(
440 DERIVE_PIPELINE derived_pipeline parent_pipeline
441 END
442 )";
443 
444   Parser parser;
445   Result r = parser.Parse(in);
446   ASSERT_FALSE(r.IsSuccess());
447   EXPECT_EQ("2: missing FROM in DERIVE_PIPELINE command", r.Error());
448 }
449 
TEST_F(AmberScriptParserTest,DerivePipelineMissingParentPipelineName)450 TEST_F(AmberScriptParserTest, DerivePipelineMissingParentPipelineName) {
451   std::string in = R"(
452 DERIVE_PIPELINE derived_pipeline FROM
453 END
454 )";
455 
456   Parser parser;
457   Result r = parser.Parse(in);
458   ASSERT_FALSE(r.IsSuccess());
459   EXPECT_EQ("3: missing parent pipeline name in DERIVE_PIPELINE command",
460             r.Error());
461 }
462 
TEST_F(AmberScriptParserTest,DerivePipelineUnknownParentPipeline)463 TEST_F(AmberScriptParserTest, DerivePipelineUnknownParentPipeline) {
464   std::string in = R"(
465 DERIVE_PIPELINE derived_pipeline FROM parent_pipeline
466 END
467 )";
468 
469   Parser parser;
470   Result r = parser.Parse(in);
471   ASSERT_FALSE(r.IsSuccess());
472   EXPECT_EQ("2: unknown parent pipeline in DERIVE_PIPELINE command", r.Error());
473 }
474 
TEST_F(AmberScriptParserTest,DerivePipelineDuplicatePipelineName)475 TEST_F(AmberScriptParserTest, DerivePipelineDuplicatePipelineName) {
476   std::string in = R"(
477 SHADER vertex my_shader PASSTHROUGH
478 SHADER fragment my_fragment GLSL
479 # GLSL Shader
480 END
481 
482 PIPELINE graphics parent_pipeline
483   ATTACH my_shader
484   ATTACH my_fragment
485 END
486 
487 DERIVE_PIPELINE parent_pipeline FROM parent_pipeline
488 END
489 )";
490 
491   Parser parser;
492   Result r = parser.Parse(in);
493   ASSERT_FALSE(r.IsSuccess());
494   EXPECT_EQ("12: duplicate pipeline name for DERIVE_PIPELINE command",
495             r.Error());
496 }
497 
TEST_F(AmberScriptParserTest,DerivePipelineNoParams)498 TEST_F(AmberScriptParserTest, DerivePipelineNoParams) {
499   std::string in = R"(
500 DERIVE_PIPELINE
501 END
502 )";
503 
504   Parser parser;
505   Result r = parser.Parse(in);
506   ASSERT_FALSE(r.IsSuccess());
507   EXPECT_EQ("3: missing pipeline name for DERIVE_PIPELINE command", r.Error());
508 }
509 
TEST_F(AmberScriptParserTest,DerivePipelineSpecialized)510 TEST_F(AmberScriptParserTest, DerivePipelineSpecialized) {
511   std::string in = R"(
512 SHADER compute my_shader GLSL
513 #shaders
514 END
515 PIPELINE compute p1
516   ATTACH my_shader SPECIALIZE 3 AS uint32 4
517 END
518 DERIVE_PIPELINE p2 FROM p1
519 END
520 )";
521 
522   Parser parser;
523   Result r = parser.Parse(in);
524   EXPECT_EQ("", r.Error());
525   ASSERT_TRUE(r.IsSuccess());
526 
527   auto script = parser.GetScript();
528   const auto& pipelines = script->GetPipelines();
529   ASSERT_EQ(2U, pipelines.size());
530 
531   const auto* p1 = pipelines[0].get();
532   const auto& s1 = p1->GetShaders();
533   ASSERT_EQ(1U, s1.size());
534 
535   EXPECT_EQ(1u, s1[0].GetSpecialization().size());
536   EXPECT_EQ(4u, s1[0].GetSpecialization().at(3));
537 
538   const auto* p2 = pipelines[1].get();
539   const auto& s2 = p2->GetShaders();
540   ASSERT_EQ(1U, s2.size());
541 
542   EXPECT_EQ(1u, s2[0].GetSpecialization().size());
543   EXPECT_EQ(4u, s2[0].GetSpecialization().at(3));
544 }
545 
TEST_F(AmberScriptParserTest,PipelinePatchControlPoints)546 TEST_F(AmberScriptParserTest, PipelinePatchControlPoints) {
547   std::string in = R"(
548 DEVICE_FEATURE tessellationShader
549 
550 SHADER vertex my_shader PASSTHROUGH
551 SHADER fragment my_fragment GLSL
552 # GLSL Shader
553 END
554 
555 SHADER tessellation_control my_tesc GLSL
556 # GLSL Shader
557 END
558 
559 SHADER tessellation_evaluation my_tese GLSL
560 # GLSL Shader
561 END
562 
563 PIPELINE graphics my_pipeline
564   ATTACH my_shader
565   ATTACH my_tesc
566   ATTACH my_tese
567   ATTACH my_fragment
568 
569   PATCH_CONTROL_POINTS 4
570 END
571 )";
572 
573   Parser parser;
574   Result r = parser.Parse(in);
575   ASSERT_TRUE(r.IsSuccess()) << r.Error();
576 
577   auto script = parser.GetScript();
578   const auto& pipelines = script->GetPipelines();
579   ASSERT_EQ(1U, pipelines.size());
580 
581   EXPECT_EQ(pipelines[0]->GetPipelineData()->GetPatchControlPoints(), 4u);
582 }
583 
TEST_F(AmberScriptParserTest,PipelineDerivePatchControlPoints)584 TEST_F(AmberScriptParserTest, PipelineDerivePatchControlPoints) {
585   std::string in = R"(
586 DEVICE_FEATURE tessellationShader
587 
588 SHADER vertex my_shader PASSTHROUGH
589 SHADER fragment my_fragment GLSL
590 # GLSL Shader
591 END
592 
593 SHADER tessellation_control my_tesc GLSL
594 # GLSL Shader
595 END
596 
597 SHADER tessellation_evaluation my_tese GLSL
598 # GLSL Shader
599 END
600 
601 PIPELINE graphics my_pipeline
602   ATTACH my_shader
603   ATTACH my_tesc
604   ATTACH my_tese
605   ATTACH my_fragment
606 
607   PATCH_CONTROL_POINTS 4
608 END
609 
610 DERIVE_PIPELINE child_pipeline FROM my_pipeline
611 END
612 )";
613 
614   Parser parser;
615   Result r = parser.Parse(in);
616   ASSERT_TRUE(r.IsSuccess()) << r.Error();
617 
618   auto script = parser.GetScript();
619   const auto& pipelines = script->GetPipelines();
620   ASSERT_EQ(2U, pipelines.size());
621 
622   EXPECT_EQ(pipelines[0]->GetPipelineData()->GetPatchControlPoints(), 4u);
623   EXPECT_EQ(pipelines[1]->GetPipelineData()->GetPatchControlPoints(), 4u);
624 }
625 
626 }  // namespace amberscript
627 }  // namespace amber
628