gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/dd/dd_test.go (about)

     1  // Copyright 2017 the u-root 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 main
     6  
     7  import (
     8  	"bufio"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"reflect"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/u-root/u-root/pkg/testutil"
    19  )
    20  
    21  // TestDd implements a table-driven test.
    22  func TestDd(t *testing.T) {
    23  	var tests = []struct {
    24  		name    string
    25  		flags   []string
    26  		stdin   string
    27  		stdout  []byte
    28  		count   int64
    29  		compare func(io.Reader, []byte, int64) error
    30  	}{
    31  
    32  		{
    33  			name:    "Simple copying from input to output",
    34  			flags:   []string{},
    35  			stdin:   "1: defaults",
    36  			stdout:  []byte("1: defaults"),
    37  			compare: stdoutEqual,
    38  		},
    39  		{
    40  			name:    "Copy from input to output on a non-aligned block size",
    41  			flags:   []string{"bs=8c"},
    42  			stdin:   "2: bs=8c 11b", // len=12 is not multiple of 8
    43  			stdout:  []byte("2: bs=8c 11b"),
    44  			compare: stdoutEqual,
    45  		},
    46  		{
    47  			name:    "Copy from input to output on an aligned block size",
    48  			flags:   []string{"bs=8"},
    49  			stdin:   "hello world.....", // len=16 is a multiple of 8
    50  			stdout:  []byte("hello world....."),
    51  			compare: stdoutEqual,
    52  		},
    53  		{
    54  			name:    "Create a 64KiB zeroed file in 1KiB blocks",
    55  			flags:   []string{"if=/dev/zero", "bs=1K", "count=64"},
    56  			stdin:   "",
    57  			stdout:  []byte("\x00"),
    58  			count:   64 * 1024,
    59  			compare: byteCount,
    60  		},
    61  		{
    62  			name:    "Create a 64KiB zeroed file in 1 byte blocks",
    63  			flags:   []string{"if=/dev/zero", "bs=1", "count=65536"},
    64  			stdin:   "",
    65  			stdout:  []byte("\x00"),
    66  			count:   64 * 1024,
    67  			compare: byteCount,
    68  		},
    69  		{
    70  			name:    "Create a 64KiB zeroed file in one 64KiB block",
    71  			flags:   []string{"if=/dev/zero", "bs=64K", "count=1"},
    72  			stdin:   "",
    73  			stdout:  []byte("\x00"),
    74  			count:   64 * 1024,
    75  			compare: byteCount,
    76  		},
    77  		{
    78  			name:    "Use skip and count",
    79  			flags:   []string{"skip=6", "bs=1", "count=5"},
    80  			stdin:   "hello world.....",
    81  			stdout:  []byte("world"),
    82  			compare: stdoutEqual,
    83  		},
    84  		{
    85  			name:    "Count clamps to end of stream",
    86  			flags:   []string{"bs=2", "skip=3", "count=100000"},
    87  			stdin:   "hello world.....",
    88  			stdout:  []byte("world....."),
    89  			compare: stdoutEqual,
    90  		},
    91  		{
    92  			name:    "512 MiB zeroed file in 1024 1KiB blocks",
    93  			flags:   []string{"bs=524288", "count=1024", "if=/dev/zero"},
    94  			stdin:   "",
    95  			stdout:  []byte("\x00"),
    96  			count:   1024 * 1024 * 512,
    97  			compare: byteCount,
    98  		},
    99  	}
   100  
   101  	for _, tt := range tests {
   102  		t.Run(tt.name, func(t *testing.T) {
   103  			cmd := testutil.Command(t, tt.flags...)
   104  			cmd.Stdin = strings.NewReader(tt.stdin)
   105  			out, err := cmd.StdoutPipe()
   106  			if err != nil {
   107  				t.Fatal(err)
   108  			}
   109  			if err := cmd.Start(); err != nil {
   110  				t.Error(err)
   111  			}
   112  			err = tt.compare(out, tt.stdout, tt.count)
   113  			if err != nil {
   114  				t.Errorf("Test compare function returned: %v", err)
   115  			}
   116  			if err := cmd.Wait(); err != nil {
   117  				t.Errorf("Test %v exited with error: %v", tt.flags, err)
   118  			}
   119  		})
   120  	}
   121  }
   122  
   123  // stdoutEqual creates a bufio Reader from io.Reader, then compares a byte at a time input []byte.
   124  // The third argument (int64) is ignored and only exists to make the function signature compatible
   125  // with func byteCount.
   126  // Returns an error if mismatch is found with offset.
   127  func stdoutEqual(i io.Reader, o []byte, _ int64) error {
   128  	var count int64
   129  	b := bufio.NewReader(i)
   130  
   131  	for {
   132  		z, err := b.ReadByte()
   133  		if err != nil {
   134  			break
   135  		}
   136  		if o[count] != z {
   137  			return fmt.Errorf("Found mismatch at offset %d, wanted %s, found %s", count, string(o[count]), string(z))
   138  		}
   139  		count++
   140  	}
   141  	return nil
   142  }
   143  
   144  // byteCount creates a bufio Reader from io.Reader, then counts the number of sequential bytes
   145  // that match the first byte in the input []byte. If the count matches input n int64, nil error
   146  // is returned. Otherwise an error is returned for a non-matching byte or if the count doesn't
   147  // match.
   148  func byteCount(i io.Reader, o []byte, n int64) error {
   149  	var count int64
   150  	buf := make([]byte, 4096)
   151  
   152  	for {
   153  		read, err := i.Read(buf)
   154  		if err != nil || read == 0 {
   155  			break
   156  		}
   157  		for z := 0; z < read; z++ {
   158  			if buf[z] == o[0] {
   159  				count++
   160  			} else {
   161  				return fmt.Errorf("Found non-matching byte: %v != %v, at offset: %d",
   162  					buf[z], o[0], count)
   163  			}
   164  		}
   165  
   166  		if count > n {
   167  			break
   168  		}
   169  	}
   170  
   171  	if count == n {
   172  		return nil
   173  	}
   174  	return fmt.Errorf("Found %d count of %#v bytes, wanted to find %d count", count, o[0], n)
   175  }
   176  
   177  // TestFiles uses `if` and `of` arguments.
   178  func TestFiles(t *testing.T) {
   179  	var tests = []struct {
   180  		name     string
   181  		flags    []string
   182  		inFile   []byte
   183  		outFile  []byte
   184  		expected []byte
   185  	}{
   186  		{
   187  			name:     "Simple copying from input to output",
   188  			flags:    []string{},
   189  			inFile:   []byte("1: defaults"),
   190  			expected: []byte("1: defaults"),
   191  		},
   192  		{
   193  			name:     "Copy from input to output on a non-aligned block size",
   194  			flags:    []string{"bs=8c"},
   195  			inFile:   []byte("2: bs=8c 11b"), // len=12 is not multiple of 8
   196  			expected: []byte("2: bs=8c 11b"),
   197  		},
   198  		{
   199  			name:     "Copy from input to output on an aligned block size",
   200  			flags:    []string{"bs=8"},
   201  			inFile:   []byte("hello world....."), // len=16 is a multiple of 8
   202  			expected: []byte("hello world....."),
   203  		},
   204  		{
   205  			name:     "Use skip and count",
   206  			flags:    []string{"skip=6", "bs=1", "count=5"},
   207  			inFile:   []byte("hello world....."),
   208  			expected: []byte("world"),
   209  		},
   210  		{
   211  			name:     "truncate",
   212  			flags:    []string{"bs=1"},
   213  			inFile:   []byte("1234"),
   214  			outFile:  []byte("abcde"),
   215  			expected: []byte("1234"),
   216  		},
   217  		{
   218  			name:     "no truncate",
   219  			flags:    []string{"bs=1", "conv=notrunc"},
   220  			inFile:   []byte("1234"),
   221  			outFile:  []byte("abcde"),
   222  			expected: []byte("1234e"),
   223  		},
   224  		{
   225  			// Fully testing the file is synchronous would require something more.
   226  			name:     "sync",
   227  			flags:    []string{"oflag=sync"},
   228  			inFile:   []byte("x: defaults"),
   229  			expected: []byte("x: defaults"),
   230  		},
   231  		{
   232  			// Fully testing the file is synchronous would require something more.
   233  			name:     "dsync",
   234  			flags:    []string{"oflag=dsync"},
   235  			inFile:   []byte("y: defaults"),
   236  			expected: []byte("y: defaults"),
   237  		},
   238  	}
   239  
   240  	for _, tt := range tests {
   241  		t.Run(tt.name, func(t *testing.T) {
   242  			// Write in and out file to temporary dir.
   243  			tmpDir, err := ioutil.TempDir("", "dd-test")
   244  			if err != nil {
   245  				t.Fatal(err)
   246  			}
   247  			defer os.RemoveAll(tmpDir)
   248  			inFile := filepath.Join(tmpDir, "inFile")
   249  			outFile := filepath.Join(tmpDir, "outFile")
   250  			if err := ioutil.WriteFile(inFile, tt.inFile, 0666); err != nil {
   251  				t.Error(err)
   252  			}
   253  			if err := ioutil.WriteFile(outFile, tt.outFile, 0666); err != nil {
   254  				t.Error(err)
   255  			}
   256  
   257  			args := append(tt.flags, "if="+inFile, "of="+outFile)
   258  			if err := testutil.Command(t, args...).Run(); err != nil {
   259  				t.Error(err)
   260  			}
   261  			got, err := ioutil.ReadFile(filepath.Join(tmpDir, "outFile"))
   262  			if err != nil {
   263  				t.Error(err)
   264  			}
   265  			if !reflect.DeepEqual(tt.expected, got) {
   266  				t.Errorf("expected %q, got %q", tt.expected, got)
   267  			}
   268  		})
   269  	}
   270  }
   271  
   272  // BenchmarkDd benchmarks the dd command. Each "op" unit is a 1MiB block.
   273  func BenchmarkDd(b *testing.B) {
   274  	const bytesPerOp = 1024 * 1024
   275  	b.SetBytes(bytesPerOp)
   276  
   277  	args := []string{
   278  		"if=/dev/zero",
   279  		"of=/dev/null",
   280  		fmt.Sprintf("count=%d", b.N),
   281  		fmt.Sprintf("bs=%d", bytesPerOp),
   282  	}
   283  	b.ResetTimer()
   284  	if err := testutil.Command(b, args...).Run(); err != nil {
   285  		b.Fatal(err)
   286  	}
   287  }
   288  
   289  func TestMain(m *testing.M) {
   290  	testutil.Run(m, main)
   291  }