github.com/rck/u-root@v0.0.0-20180106144920-7eb602e381bb/cmds/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  	"os"
    12  	"os/exec"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/u-root/u-root/pkg/testutil"
    17  )
    18  
    19  // TestDd implements a table-driven test.
    20  func TestDd(t *testing.T) {
    21  	var tests = []struct {
    22  		name    string
    23  		flags   []string
    24  		stdin   string
    25  		stdout  []byte
    26  		count   int64
    27  		compare func(io.Reader, []byte, int64) error
    28  	}{
    29  
    30  		{
    31  			name:    "Simple copying from input to output",
    32  			flags:   []string{},
    33  			stdin:   "1: defaults",
    34  			stdout:  []byte("1: defaults"),
    35  			compare: stdoutEqual,
    36  		},
    37  		{
    38  			name:    "Copy from input to output on a non-aligned block size",
    39  			flags:   []string{"bs=8c"},
    40  			stdin:   "2: bs=8c 11b", // len=12 is not multiple of 8
    41  			stdout:  []byte("2: bs=8c 11b"),
    42  			compare: stdoutEqual,
    43  		},
    44  		{
    45  			name:    "case lower change",
    46  			flags:   []string{"bs=8", "conv=lcase"},
    47  			stdin:   "3: Bs=8 11B", // len=11 is not multiple of 8
    48  			stdout:  []byte("3: bs=8 11b"),
    49  			compare: stdoutEqual,
    50  		},
    51  		{
    52  			name:    "case upper change",
    53  			flags:   []string{"bs=8", "conv=ucase"},
    54  			stdin:   "3: Bs=8 11B", // len=11 is not multiple of 8
    55  			stdout:  []byte("3: BS=8 11B"),
    56  			compare: stdoutEqual,
    57  		},
    58  		{
    59  			name:    "Copy from input to output on an aligned block size",
    60  			flags:   []string{"bs=8"},
    61  			stdin:   "hello world.....", // len=16 is a multiple of 8
    62  			stdout:  []byte("hello world....."),
    63  			compare: stdoutEqual,
    64  		},
    65  		{
    66  			name:    "Create a 64KiB zeroed file in 1KiB blocks",
    67  			flags:   []string{"if=/dev/zero", "bs=1K", "count=64"},
    68  			stdin:   "",
    69  			stdout:  []byte("\x00"),
    70  			count:   64 * 1024,
    71  			compare: byteCount,
    72  		},
    73  		{
    74  			name:    "Create a 64KiB zeroed file in 1 byte blocks",
    75  			flags:   []string{"if=/dev/zero", "bs=1", "count=65536"},
    76  			stdin:   "",
    77  			stdout:  []byte("\x00"),
    78  			count:   64 * 1024,
    79  			compare: byteCount,
    80  		},
    81  		{
    82  			name:    "Create a 64KiB zeroed file in one 64KiB block",
    83  			flags:   []string{"if=/dev/zero", "bs=64K", "count=1"},
    84  			stdin:   "",
    85  			stdout:  []byte("\x00"),
    86  			count:   64 * 1024,
    87  			compare: byteCount,
    88  		},
    89  		{
    90  			name:    "Use skip and count",
    91  			flags:   []string{"skip=6", "bs=1", "count=5"},
    92  			stdin:   "hello world.....",
    93  			stdout:  []byte("world"),
    94  			compare: stdoutEqual,
    95  		},
    96  		{
    97  			name:    "Count clamps to end of stream",
    98  			flags:   []string{"bs=2", "skip=3", "count=100000"},
    99  			stdin:   "hello world.....",
   100  			stdout:  []byte("world....."),
   101  			compare: stdoutEqual,
   102  		},
   103  		{
   104  			name:    "1 GiB zeroed file in 1024 1KiB blocks",
   105  			flags:   []string{"bs=1048576", "count=1024", "if=/dev/zero"},
   106  			stdin:   "",
   107  			stdout:  []byte("\x00"),
   108  			count:   1024 * 1024 * 1024,
   109  			compare: byteCount,
   110  		},
   111  	}
   112  	tmpDir, execPath := testutil.CompileInTempDir(t)
   113  	defer os.RemoveAll(tmpDir)
   114  
   115  	for _, tt := range tests {
   116  		t.Run(tt.name, func(t *testing.T) {
   117  			cmd := exec.Command(execPath, tt.flags...)
   118  			cmd.Stdin = strings.NewReader(tt.stdin)
   119  			out, err := cmd.StdoutPipe()
   120  			if err != nil {
   121  				t.Errorf("Test %v exited with error: %v", tt.flags, err)
   122  			}
   123  			if err := cmd.Start(); err != nil {
   124  				t.Errorf("Test %v exited with error: %v", tt.flags, err)
   125  			}
   126  			err = tt.compare(out, tt.stdout, tt.count)
   127  			if err != nil {
   128  				t.Errorf("Test compare function returned: %v", err)
   129  			}
   130  			if err := cmd.Wait(); err != nil {
   131  				t.Errorf("Test %v exited with error: %v", tt.flags, err)
   132  			}
   133  		})
   134  	}
   135  }
   136  
   137  // stdoutEqual creates a bufio Reader from io.Reader, then compares a byte at a time input []byte.
   138  // The third argument (int64) is ignored and only exists to make the function signature compatible
   139  // with func byteCount.
   140  // Returns an error if mismatch is found with offset.
   141  func stdoutEqual(i io.Reader, o []byte, _ int64) error {
   142  	var count int64
   143  	b := bufio.NewReader(i)
   144  
   145  	for {
   146  		z, err := b.ReadByte()
   147  		if err != nil {
   148  			break
   149  		}
   150  		if o[count] != z {
   151  			return fmt.Errorf("Found mismatch at offset %d, wanted %s, found %s", count, string(o[count]), string(z))
   152  		}
   153  		count++
   154  	}
   155  	return nil
   156  }
   157  
   158  // byteCount creates a bufio Reader from io.Reader, then counts the number of sequential bytes
   159  // that match the first byte in the input []byte. If the count matches input n int64, nil error
   160  // is returned. Otherwise an error is returned for a non-matching byte or if the count doesn't
   161  // match.
   162  func byteCount(i io.Reader, o []byte, n int64) error {
   163  	b := bufio.NewReader(i)
   164  	var count int64
   165  
   166  	for {
   167  		z, err := b.ReadByte()
   168  		if err != nil {
   169  			break
   170  		}
   171  		if z == o[0] {
   172  			count++
   173  		} else {
   174  			return fmt.Errorf("Found non-matching byte: %v, at offset: %d", o[0], count)
   175  		}
   176  	}
   177  
   178  	if count == n {
   179  		return nil
   180  	}
   181  	return fmt.Errorf("Found %d count of %#v bytes, wanted to find %d count", count, o[0], n)
   182  }
   183  
   184  // BenchmarkDd benchmarks the dd command. Each "op" unit is a 1MiB block.
   185  func BenchmarkDd(b *testing.B) {
   186  	tmpDir, execPath := testutil.CompileInTempDir(b)
   187  	defer os.RemoveAll(tmpDir)
   188  
   189  	const bytesPerOp = 1024 * 1024
   190  	b.SetBytes(bytesPerOp)
   191  	args := []string{
   192  		"if=/dev/zero",
   193  		"of=/dev/null",
   194  		fmt.Sprintf("count=%d", b.N),
   195  		fmt.Sprintf("bs=%d", bytesPerOp),
   196  	}
   197  	b.ResetTimer()
   198  	if err := exec.Command(execPath, args...).Run(); err != nil {
   199  		b.Fatal(err)
   200  	}
   201  }