Source file src/mime/type_test.go

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package mime
     6  
     7  import (
     8  	"internal/asan"
     9  	"slices"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  )
    14  
    15  func setMimeInit(fn func()) (cleanup func()) {
    16  	once = sync.Once{}
    17  	testInitMime = fn
    18  	return func() {
    19  		testInitMime = nil
    20  		once = sync.Once{}
    21  	}
    22  }
    23  
    24  func clearMimeTypes() {
    25  	setMimeTypes(map[string]string{}, map[string]string{})
    26  }
    27  
    28  func setType(ext, typ string) {
    29  	if !strings.HasPrefix(ext, ".") {
    30  		panic("missing leading dot")
    31  	}
    32  	if err := setExtensionType(ext, typ); err != nil {
    33  		panic("bad test data: " + err.Error())
    34  	}
    35  }
    36  
    37  func TestTypeByExtension(t *testing.T) {
    38  	once = sync.Once{}
    39  	// initMimeForTests returns the platform-specific extension =>
    40  	// type tests. On Unix and Plan 9, this also tests the parsing
    41  	// of MIME text files (in testdata/*). On Windows, we test the
    42  	// real registry on the machine and assume that ".png" exists
    43  	// there, which empirically it always has, for all versions of
    44  	// Windows.
    45  	typeTests := initMimeForTests()
    46  
    47  	for ext, want := range typeTests {
    48  		val := TypeByExtension(ext)
    49  		if val != want {
    50  			t.Errorf("TypeByExtension(%q) = %q, want %q", ext, val, want)
    51  		}
    52  	}
    53  }
    54  
    55  func TestTypeByExtension_LocalData(t *testing.T) {
    56  	cleanup := setMimeInit(func() {
    57  		clearMimeTypes()
    58  		setType(".foo", "x/foo")
    59  		setType(".bar", "x/bar")
    60  		setType(".Bar", "x/bar; capital=1")
    61  	})
    62  	defer cleanup()
    63  
    64  	tests := map[string]string{
    65  		".foo":          "x/foo",
    66  		".bar":          "x/bar",
    67  		".Bar":          "x/bar; capital=1",
    68  		".sdlkfjskdlfj": "",
    69  		".t1":           "", // testdata shouldn't be used
    70  	}
    71  
    72  	for ext, want := range tests {
    73  		val := TypeByExtension(ext)
    74  		if val != want {
    75  			t.Errorf("TypeByExtension(%q) = %q, want %q", ext, val, want)
    76  		}
    77  	}
    78  }
    79  
    80  func TestTypeByExtensionCase(t *testing.T) {
    81  	const custom = "test/test; charset=iso-8859-1"
    82  	const caps = "test/test; WAS=ALLCAPS"
    83  
    84  	cleanup := setMimeInit(func() {
    85  		clearMimeTypes()
    86  		setType(".TEST", caps)
    87  		setType(".tesT", custom)
    88  	})
    89  	defer cleanup()
    90  
    91  	// case-sensitive lookup
    92  	if got := TypeByExtension(".tesT"); got != custom {
    93  		t.Fatalf("for .tesT, got %q; want %q", got, custom)
    94  	}
    95  	if got := TypeByExtension(".TEST"); got != caps {
    96  		t.Fatalf("for .TEST, got %q; want %s", got, caps)
    97  	}
    98  
    99  	// case-insensitive
   100  	if got := TypeByExtension(".TesT"); got != custom {
   101  		t.Fatalf("for .TesT, got %q; want %q", got, custom)
   102  	}
   103  }
   104  
   105  func TestExtensionsByType(t *testing.T) {
   106  	cleanup := setMimeInit(func() {
   107  		clearMimeTypes()
   108  		setType(".gif", "image/gif")
   109  		setType(".a", "foo/letter")
   110  		setType(".b", "foo/letter")
   111  		setType(".B", "foo/letter")
   112  		setType(".PNG", "image/png")
   113  	})
   114  	defer cleanup()
   115  
   116  	tests := []struct {
   117  		typ     string
   118  		want    []string
   119  		wantErr string
   120  	}{
   121  		{typ: "image/gif", want: []string{".gif"}},
   122  		{typ: "image/png", want: []string{".png"}}, // lowercase
   123  		{typ: "foo/letter", want: []string{".a", ".b"}},
   124  		{typ: "x/unknown", want: nil},
   125  	}
   126  
   127  	for _, tt := range tests {
   128  		got, err := ExtensionsByType(tt.typ)
   129  		if err != nil && tt.wantErr != "" && strings.Contains(err.Error(), tt.wantErr) {
   130  			continue
   131  		}
   132  		if err != nil {
   133  			t.Errorf("ExtensionsByType(%q) error: %v", tt.typ, err)
   134  			continue
   135  		}
   136  		if tt.wantErr != "" {
   137  			t.Errorf("ExtensionsByType(%q) = %q, %v; want error substring %q", tt.typ, got, err, tt.wantErr)
   138  			continue
   139  		}
   140  		if !slices.Equal(got, tt.want) {
   141  			t.Errorf("ExtensionsByType(%q) = %q; want %q", tt.typ, got, tt.want)
   142  		}
   143  	}
   144  }
   145  
   146  func TestLookupMallocs(t *testing.T) {
   147  	if asan.Enabled {
   148  		t.Skip("test allocates more with -asan; see #70079")
   149  	}
   150  	n := testing.AllocsPerRun(10000, func() {
   151  		TypeByExtension(".html")
   152  		TypeByExtension(".HtML")
   153  	})
   154  	if n > 0 {
   155  		t.Errorf("allocs = %v; want 0", n)
   156  	}
   157  }
   158  
   159  func BenchmarkTypeByExtension(b *testing.B) {
   160  	initMime()
   161  	b.ResetTimer()
   162  
   163  	for _, ext := range []string{
   164  		".html",
   165  		".HTML",
   166  		".unused",
   167  	} {
   168  		b.Run(ext, func(b *testing.B) {
   169  			b.RunParallel(func(pb *testing.PB) {
   170  				for pb.Next() {
   171  					TypeByExtension(ext)
   172  				}
   173  			})
   174  		})
   175  	}
   176  }
   177  
   178  func BenchmarkExtensionsByType(b *testing.B) {
   179  	initMime()
   180  	b.ResetTimer()
   181  
   182  	for _, typ := range []string{
   183  		"text/html",
   184  		"text/html; charset=utf-8",
   185  		"application/octet-stream",
   186  	} {
   187  		b.Run(typ, func(b *testing.B) {
   188  			b.RunParallel(func(pb *testing.PB) {
   189  				for pb.Next() {
   190  					if _, err := ExtensionsByType(typ); err != nil {
   191  						b.Fatal(err)
   192  					}
   193  				}
   194  			})
   195  		})
   196  	}
   197  }
   198  
   199  func TestExtensionsByType2(t *testing.T) {
   200  	cleanup := setMimeInit(func() {
   201  		clearMimeTypes()
   202  		// Initialize built-in types like in type.go before osInitMime.
   203  		setMimeTypes(builtinTypesLower, builtinTypesLower)
   204  	})
   205  	defer cleanup()
   206  
   207  	tests := []struct {
   208  		typ  string
   209  		want []string
   210  	}{
   211  		{typ: "image/jpeg", want: []string{".jpeg", ".jpg"}},
   212  	}
   213  
   214  	for _, tt := range tests {
   215  		got, err := ExtensionsByType(tt.typ)
   216  		if err != nil {
   217  			t.Errorf("ExtensionsByType(%q): %v", tt.typ, err)
   218  			continue
   219  		}
   220  		if !slices.Equal(got, tt.want) {
   221  			t.Errorf("ExtensionsByType(%q) = %q; want %q", tt.typ, got, tt.want)
   222  		}
   223  	}
   224  }
   225  

View as plain text