parse_test.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package instructions // import "github.com/docker/docker/builder/dockerfile/instructions"
  2. import (
  3. "strings"
  4. "testing"
  5. "github.com/docker/docker/builder/dockerfile/command"
  6. "github.com/docker/docker/builder/dockerfile/parser"
  7. "github.com/gotestyourself/gotestyourself/assert"
  8. is "github.com/gotestyourself/gotestyourself/assert/cmp"
  9. )
  10. func TestCommandsExactlyOneArgument(t *testing.T) {
  11. commands := []string{
  12. "MAINTAINER",
  13. "WORKDIR",
  14. "USER",
  15. "STOPSIGNAL",
  16. }
  17. for _, cmd := range commands {
  18. ast, err := parser.Parse(strings.NewReader(cmd))
  19. assert.NilError(t, err)
  20. _, err = ParseInstruction(ast.AST.Children[0])
  21. assert.Check(t, is.Error(err, errExactlyOneArgument(cmd).Error()))
  22. }
  23. }
  24. func TestCommandsAtLeastOneArgument(t *testing.T) {
  25. commands := []string{
  26. "ENV",
  27. "LABEL",
  28. "ONBUILD",
  29. "HEALTHCHECK",
  30. "EXPOSE",
  31. "VOLUME",
  32. }
  33. for _, cmd := range commands {
  34. ast, err := parser.Parse(strings.NewReader(cmd))
  35. assert.NilError(t, err)
  36. _, err = ParseInstruction(ast.AST.Children[0])
  37. assert.Check(t, is.Error(err, errAtLeastOneArgument(cmd).Error()))
  38. }
  39. }
  40. func TestCommandsNoDestinationArgument(t *testing.T) {
  41. commands := []string{
  42. "ADD",
  43. "COPY",
  44. }
  45. for _, cmd := range commands {
  46. ast, err := parser.Parse(strings.NewReader(cmd + " arg1"))
  47. assert.NilError(t, err)
  48. _, err = ParseInstruction(ast.AST.Children[0])
  49. assert.Check(t, is.Error(err, errNoDestinationArgument(cmd).Error()))
  50. }
  51. }
  52. func TestCommandsTooManyArguments(t *testing.T) {
  53. commands := []string{
  54. "ENV",
  55. "LABEL",
  56. }
  57. for _, command := range commands {
  58. node := &parser.Node{
  59. Original: command + "arg1 arg2 arg3",
  60. Value: strings.ToLower(command),
  61. Next: &parser.Node{
  62. Value: "arg1",
  63. Next: &parser.Node{
  64. Value: "arg2",
  65. Next: &parser.Node{
  66. Value: "arg3",
  67. },
  68. },
  69. },
  70. }
  71. _, err := ParseInstruction(node)
  72. assert.Check(t, is.Error(err, errTooManyArguments(command).Error()))
  73. }
  74. }
  75. func TestCommandsBlankNames(t *testing.T) {
  76. commands := []string{
  77. "ENV",
  78. "LABEL",
  79. }
  80. for _, cmd := range commands {
  81. node := &parser.Node{
  82. Original: cmd + " =arg2",
  83. Value: strings.ToLower(cmd),
  84. Next: &parser.Node{
  85. Value: "",
  86. Next: &parser.Node{
  87. Value: "arg2",
  88. },
  89. },
  90. }
  91. _, err := ParseInstruction(node)
  92. assert.Check(t, is.Error(err, errBlankCommandNames(cmd).Error()))
  93. }
  94. }
  95. func TestHealthCheckCmd(t *testing.T) {
  96. node := &parser.Node{
  97. Value: command.Healthcheck,
  98. Next: &parser.Node{
  99. Value: "CMD",
  100. Next: &parser.Node{
  101. Value: "hello",
  102. Next: &parser.Node{
  103. Value: "world",
  104. },
  105. },
  106. },
  107. }
  108. cmd, err := ParseInstruction(node)
  109. assert.Check(t, err)
  110. hc, ok := cmd.(*HealthCheckCommand)
  111. assert.Check(t, ok)
  112. expected := []string{"CMD-SHELL", "hello world"}
  113. assert.Check(t, is.DeepEqual(expected, hc.Health.Test))
  114. }
  115. func TestParseOptInterval(t *testing.T) {
  116. flInterval := &Flag{
  117. name: "interval",
  118. flagType: stringType,
  119. Value: "50ns",
  120. }
  121. _, err := parseOptInterval(flInterval)
  122. assert.Check(t, is.ErrorContains(err, "cannot be less than 1ms"))
  123. flInterval.Value = "1ms"
  124. _, err = parseOptInterval(flInterval)
  125. assert.NilError(t, err)
  126. }
  127. func TestErrorCases(t *testing.T) {
  128. cases := []struct {
  129. name string
  130. dockerfile string
  131. expectedError string
  132. }{
  133. {
  134. name: "copyEmptyWhitespace",
  135. dockerfile: `COPY
  136. quux \
  137. bar`,
  138. expectedError: "COPY requires at least two arguments",
  139. },
  140. {
  141. name: "ONBUILD forbidden FROM",
  142. dockerfile: "ONBUILD FROM scratch",
  143. expectedError: "FROM isn't allowed as an ONBUILD trigger",
  144. },
  145. {
  146. name: "ONBUILD forbidden MAINTAINER",
  147. dockerfile: "ONBUILD MAINTAINER docker.io",
  148. expectedError: "MAINTAINER isn't allowed as an ONBUILD trigger",
  149. },
  150. {
  151. name: "ARG two arguments",
  152. dockerfile: "ARG foo bar",
  153. expectedError: "ARG requires exactly one argument",
  154. },
  155. {
  156. name: "MAINTAINER unknown flag",
  157. dockerfile: "MAINTAINER --boo joe@example.com",
  158. expectedError: "Unknown flag: boo",
  159. },
  160. {
  161. name: "Chaining ONBUILD",
  162. dockerfile: `ONBUILD ONBUILD RUN touch foobar`,
  163. expectedError: "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
  164. },
  165. {
  166. name: "Invalid instruction",
  167. dockerfile: `foo bar`,
  168. expectedError: "unknown instruction: FOO",
  169. },
  170. }
  171. for _, c := range cases {
  172. r := strings.NewReader(c.dockerfile)
  173. ast, err := parser.Parse(r)
  174. if err != nil {
  175. t.Fatalf("Error when parsing Dockerfile: %s", err)
  176. }
  177. n := ast.AST.Children[0]
  178. _, err = ParseInstruction(n)
  179. assert.Check(t, is.ErrorContains(err, c.expectedError))
  180. }
  181. }