parse_test.go 4.9 KB

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