parse_test.go 4.6 KB

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