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: "application/postscript", want: []string{".ai", ".eps", ".ps"}},
   212  		{typ: "application/vnd.android.package-archive", want: []string{".apk"}},
   213  		{typ: "image/apng", want: []string{".apng"}},
   214  		{typ: "image/avif", want: []string{".avif"}},
   215  		{typ: "application/octet-stream", want: []string{".bin", ".com", ".exe"}},
   216  		{typ: "image/bmp", want: []string{".bmp"}},
   217  		{typ: "text/css; charset=utf-8", want: []string{".css"}},
   218  		{typ: "text/csv; charset=utf-8", want: []string{".csv"}},
   219  		{typ: "application/msword", want: []string{".doc"}},
   220  		{typ: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", want: []string{".docx"}},
   221  		{typ: "text/html; charset=utf-8", want: []string{".ehtml", ".htm", ".html", ".shtml"}},
   222  		{typ: "message/rfc822", want: []string{".eml"}},
   223  		{typ: "audio/flac", want: []string{".flac"}},
   224  		{typ: "image/gif", want: []string{".gif"}},
   225  		{typ: "application/gzip", want: []string{".gz"}},
   226  		{typ: "image/vnd.microsoft.icon", want: []string{".ico"}},
   227  		{typ: "text/calendar; charset=utf-8", want: []string{".ics"}},
   228  		{typ: "image/jpeg", want: []string{".jfif", ".jpeg", ".jpg", ".pjp", ".pjpeg"}},
   229  		{typ: "text/javascript; charset=utf-8", want: []string{".js", ".mjs"}},
   230  		{typ: "application/json", want: []string{".json"}},
   231  		{typ: "audio/mp4", want: []string{".m4a"}},
   232  		{typ: "audio/mpeg", want: []string{".mp3"}},
   233  		{typ: "video/mp4", want: []string{".mp4"}},
   234  		{typ: "audio/ogg", want: []string{".oga", ".ogg", ".opus"}},
   235  		{typ: "video/ogg", want: []string{".ogv"}},
   236  		{typ: "application/pdf", want: []string{".pdf"}},
   237  		{typ: "image/png", want: []string{".png"}},
   238  		{typ: "application/vnd.ms-powerpoint", want: []string{".ppt"}},
   239  		{typ: "application/vnd.openxmlformats-officedocument.presentationml.presentation", want: []string{".pptx"}},
   240  		{typ: "application/rdf+xml", want: []string{".rdf"}},
   241  		{typ: "application/rtf", want: []string{".rtf"}},
   242  		{typ: "image/svg+xml", want: []string{".svg"}},
   243  		{typ: "text/plain; charset=utf-8", want: []string{".text", ".txt"}},
   244  		{typ: "image/tiff", want: []string{".tif", ".tiff"}},
   245  		{typ: "text/vtt; charset=utf-8", want: []string{".vtt"}},
   246  		{typ: "application/wasm", want: []string{".wasm"}},
   247  		{typ: "audio/wav", want: []string{".wav"}},
   248  		{typ: "audio/webm", want: []string{".webm"}},
   249  		{typ: "image/webp", want: []string{".webp"}},
   250  		{typ: "text/xml; charset=utf-8", want: []string{".xbl", ".xml", ".xsl"}},
   251  		{typ: "image/x-xbitmap", want: []string{".xbm"}},
   252  		{typ: "application/xhtml+xml", want: []string{".xht", ".xhtml"}},
   253  		{typ: "application/vnd.ms-excel", want: []string{".xls"}},
   254  		{typ: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", want: []string{".xlsx"}},
   255  		{typ: "application/zip", want: []string{".zip"}},
   256  	}
   257  
   258  	for _, tt := range tests {
   259  		got, err := ExtensionsByType(tt.typ)
   260  		if err != nil {
   261  			t.Errorf("ExtensionsByType(%q): %v", tt.typ, err)
   262  			continue
   263  		}
   264  		if !slices.Equal(got, tt.want) {
   265  			t.Errorf("ExtensionsByType(%q) = %q; want %q", tt.typ, got, tt.want)
   266  		}
   267  	}
   268  }
   269  

View as plain text