...

Source file src/github.com/opencontainers/image-spec/schema/spec_test.go

Documentation: github.com/opencontainers/image-spec/schema

     1  // Copyright 2016 The Linux Foundation
     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 implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package schema_test
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"net/url"
    23  	"os"
    24  	"path/filepath"
    25  	"reflect"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/opencontainers/image-spec/schema"
    30  	"github.com/russross/blackfriday"
    31  )
    32  
    33  var (
    34  	errFormatInvalid = errors.New("format: invalid")
    35  )
    36  
    37  func TestValidateDescriptor(t *testing.T) {
    38  	validate(t, "../descriptor.md")
    39  }
    40  
    41  func TestValidateManifest(t *testing.T) {
    42  	validate(t, "../manifest.md")
    43  }
    44  
    45  func TestValidateImageIndex(t *testing.T) {
    46  	validate(t, "../image-index.md")
    47  }
    48  
    49  func TestValidateImageLayout(t *testing.T) {
    50  	validate(t, "../image-layout.md")
    51  }
    52  
    53  func TestValidateConfig(t *testing.T) {
    54  	validate(t, "../config.md")
    55  }
    56  
    57  func TestSchemaFS(t *testing.T) {
    58  	expectedSchemaFileNames, err := filepath.Glob("*.json")
    59  	if err != nil {
    60  		t.Error(err)
    61  	}
    62  
    63  	dir, err := schema.FileSystem().Open("/")
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  
    68  	files, err := dir.Readdir(-1)
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  	var schemaFileNames []string
    73  	for _, f := range files {
    74  		schemaFileNames = append(schemaFileNames, f.Name())
    75  	}
    76  
    77  	if !reflect.DeepEqual(schemaFileNames, expectedSchemaFileNames) {
    78  		t.Fatalf("got %v, expected %v", schemaFileNames, expectedSchemaFileNames)
    79  	}
    80  }
    81  
    82  // TODO(sur): include examples from all specification files
    83  func validate(t *testing.T, name string) {
    84  	m, err := os.Open(name)
    85  	if err != nil {
    86  		t.Fatal(err)
    87  	}
    88  	defer m.Close()
    89  
    90  	examples, err := extractExamples(m)
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  
    95  	for _, example := range examples {
    96  		if errors.Is(example.Err, errFormatInvalid) && example.Mediatype == "" { // ignore
    97  			continue
    98  		}
    99  
   100  		if example.Err != nil {
   101  			printFields(t, "error", example.Mediatype, example.Title, example.Err)
   102  			t.Error(err)
   103  			continue
   104  		}
   105  
   106  		err = schema.Validator(example.Mediatype).Validate(strings.NewReader(example.Body))
   107  		if err == nil {
   108  			printFields(t, "ok", example.Mediatype, example.Title)
   109  			t.Log(example.Body, "---")
   110  			continue
   111  		}
   112  
   113  		var errs []error
   114  		var verr schema.ValidationError
   115  		if errors.As(err, &verr) {
   116  			errs = verr.Errs
   117  		} else {
   118  			printFields(t, "error", example.Mediatype, example.Title, err)
   119  			t.Error(err)
   120  			t.Log(example.Body, "---")
   121  			continue
   122  		}
   123  
   124  		for _, err := range errs {
   125  			printFields(t, "invalid", example.Mediatype, example.Title)
   126  			t.Error(err)
   127  			fmt.Println(example.Body, "---")
   128  			continue
   129  		}
   130  	}
   131  }
   132  
   133  // renderer allows one to incercept fenced blocks in markdown documents.
   134  type renderer struct {
   135  	blackfriday.Renderer
   136  	fn func(text []byte, lang string)
   137  }
   138  
   139  func (r *renderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
   140  	r.fn(text, lang)
   141  	r.Renderer.BlockCode(out, text, lang)
   142  }
   143  
   144  type example struct {
   145  	Lang      string // gets raw "lang" field
   146  	Title     string
   147  	Mediatype string
   148  	Body      string
   149  	Err       error
   150  
   151  	// TODO(stevvooe): Figure out how to keep track of revision, file, line so
   152  	// that we can trace back verification output.
   153  }
   154  
   155  // parseExample treats the field as a syntax,attribute tuple separated by a comma.
   156  // Attributes are encoded as a url values.
   157  //
   158  // An example of this is `json,title=Foo%20Bar&mediatype=application/json. We
   159  // get that the "lang" is json, the title is "Foo Bar" and the mediatype is
   160  // "application/json".
   161  //
   162  // This preserves syntax highlighting and lets us tag examples with further
   163  // metadata.
   164  func parseExample(lang, body string) (e example) {
   165  	e.Lang = lang
   166  	e.Body = body
   167  
   168  	parts := strings.SplitN(lang, ",", 2)
   169  	if len(parts) < 2 {
   170  		e.Err = errFormatInvalid
   171  		return
   172  	}
   173  
   174  	m, err := url.ParseQuery(parts[1])
   175  	if err != nil {
   176  		e.Err = err
   177  		return
   178  	}
   179  
   180  	e.Mediatype = m.Get("mediatype")
   181  	e.Title = m.Get("title")
   182  	return
   183  }
   184  
   185  func extractExamples(rd io.Reader) ([]example, error) {
   186  	p, err := io.ReadAll(rd)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	var examples []example
   192  	renderer := &renderer{
   193  		Renderer: blackfriday.HtmlRenderer(0, "test test", ""),
   194  		fn: func(text []byte, lang string) {
   195  			examples = append(examples, parseExample(lang, string(text)))
   196  		},
   197  	}
   198  
   199  	// just pass over the markdown and ignore the rendered result. We just want
   200  	// the side-effect of calling back for each code block.
   201  	// TODO(stevvooe): Consider just parsing these with a scanner. It will be
   202  	// faster and we can retain file, line no.
   203  	blackfriday.MarkdownOptions(p, renderer, blackfriday.Options{
   204  		Extensions: blackfriday.EXTENSION_FENCED_CODE,
   205  	})
   206  
   207  	return examples, nil
   208  }
   209  
   210  // printFields prints each value tab separated.
   211  func printFields(t *testing.T, vs ...interface{}) {
   212  	var ss []string
   213  	for _, f := range vs {
   214  		ss = append(ss, fmt.Sprint(f))
   215  	}
   216  	t.Log(strings.Join(ss, "\t"))
   217  }
   218  

View as plain text