diff --git a/fs/hdfs/create_test.go b/fs/hdfs/create_test.go
new file mode 100644
index 0000000..6b701b2
--- /dev/null
+++ b/fs/hdfs/create_test.go
@@ -0,0 +1,91 @@
+package hdfs
+
+import (
+ "io"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_Create(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+
+ f, err := fs.Create("/hello.txt")
+ r.NoError(err)
+ r.NotNil(f)
+
+ fi, err := f.Stat()
+ r.NoError(err)
+
+ r.Equal("/hello.txt", fi.Name())
+ r.Equal(os.FileMode(0666), fi.Mode())
+ r.NotZero(fi.ModTime())
+}
+
+func Test_Create_Write(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+
+ f, err := fs.Create("/hello.txt")
+ r.NoError(err)
+ r.NotNil(f)
+
+ fi, err := f.Stat()
+ r.NoError(err)
+ r.Zero(fi.Size())
+
+ r.Equal("/hello.txt", fi.Name())
+
+ mt := fi.ModTime()
+ r.NotZero(mt)
+
+ sz, err := io.Copy(f, strings.NewReader(radio))
+ r.NoError(err)
+ r.Equal(int64(1381), sz)
+
+ r.NoError(f.Close())
+ r.Equal(int64(1381), fi.Size())
+ r.NotZero(fi.ModTime())
+ r.NotEqual(mt, fi.ModTime())
+}
+
+const radio = `I was tuning in the shine on the late night dial
+Doing anything my radio advised
+With every one of those late night stations
+Playing songs bringing tears to my eyes
+I was seriously thinking about hiding the receiver
+When the switch broke 'cause it's old
+They're saying things that I can hardly believe
+They really think we're getting out of control
+Radio is a sound salvation
+Radio is cleaning up the nation
+They say you better listen to the voice of reason
+But they don't give you any choice 'cause they think that it's treason
+So you had better do as you are told
+You better listen to the radio
+I wanna bite the hand that feeds me
+I wanna bite that hand so badly
+I want to make them wish they'd never seen me
+Some of my friends sit around every evening
+And they worry about the times ahead
+But everybody else is overwhelmed by indifference
+And the promise of an early bed
+You either shut up or get cut up; they don't wanna hear about it
+It's only inches on the reel-to-reel
+And the radio is in the hands of such a lot of fools
+Tryin' to anesthetize the way that you feel
+Radio is a sound salvation
+Radio is cleaning up the nation
+They say you better listen to the voice of reason
+But they don't give you any choice 'cause they think that it's treason
+So you had better do as you are told
+You better listen to the radio
+Wonderful radio
+Marvelous radio
+Wonderful radio
+Radio, radio`
diff --git a/fs/hdfs/file_test.go b/fs/hdfs/file_test.go
new file mode 100644
index 0000000..7c6ca0f
--- /dev/null
+++ b/fs/hdfs/file_test.go
@@ -0,0 +1,67 @@
+package hdfs
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_File_Read_Memory(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+
+ f, err := fs.Create("/file_test.go")
+ r.NoError(err)
+ _, err = io.Copy(f, bytes.NewReader([]byte("hi!")))
+ r.NoError(err)
+ r.NoError(f.Close())
+
+ f, err = fs.Open("/file_test.go")
+ r.NoError(err)
+ fi, err := f.Stat()
+ r.NoError(err)
+ r.Equal("/file_test.go", fi.Name())
+
+ b, err := ioutil.ReadAll(f)
+ r.NoError(err)
+ r.Equal(string(b), "hi!")
+}
+
+func Test_File_Write(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+
+ f, err := fs.Create("/hello.txt")
+ r.NoError(err)
+ r.NotNil(f)
+
+ fi, err := f.Stat()
+ r.NoError(err)
+ r.Zero(fi.Size())
+
+ r.Equal("/hello.txt", fi.Name())
+
+ mt := fi.ModTime()
+ r.NotZero(mt)
+
+ sz, err := io.Copy(f, strings.NewReader(radio))
+ r.NoError(err)
+ r.Equal(int64(1381), sz)
+
+ // because windows can't handle the time precisely
+ // enough, we have to *force* just a smidge of time
+ // to ensure the two ModTime's are different.
+ // i know, i hate it too.
+ time.Sleep(time.Millisecond)
+ r.NoError(f.Close())
+ r.Equal(int64(1381), fi.Size())
+ r.NotZero(fi.ModTime())
+ r.NotEqual(mt, fi.ModTime())
+}
diff --git a/fs/hdfs/hdfs_test.go b/fs/hdfs/hdfs_test.go
new file mode 100644
index 0000000..6d52926
--- /dev/null
+++ b/fs/hdfs/hdfs_test.go
@@ -0,0 +1,23 @@
+package hdfs
+
+import (
+ "log"
+
+ "github.com/markbates/pkger/fs/fstest"
+)
+
+func NewFS() *FS {
+ fs, err := New()
+ if err != nil {
+ log.Fatal(err)
+ }
+ return fs
+}
+
+var Folder = fstest.TestFiles{
+ "/testdata/hdfs_test/main.go": {Data: []byte("!/testdata/hdfs_test/main.go")},
+ "/testdata/hdfs_test/public/index.html": {Data: []byte("!/testdata/hdfs_test/public/index.html")},
+ "/testdata/hdfs_test/public/images/mark.png": {Data: []byte("!/testdata/hdfs_test/public/images/mark.png")},
+ "/testdata/hdfs_test/templates/a.txt": {Data: []byte("!/testdata/hdfs_test/templates/a.txt")},
+ "/testdata/hdfs_test/templates/b/b.txt": {Data: []byte("!/testdata/hdfs_test/templates/b/b.txt")},
+}
diff --git a/fs/hdfs/hello.txt b/fs/hdfs/hello.txt
new file mode 100644
index 0000000..e69de29
diff --git a/fs/hdfs/http_test.go b/fs/hdfs/http_test.go
new file mode 100644
index 0000000..c97e85f
--- /dev/null
+++ b/fs/hdfs/http_test.go
@@ -0,0 +1,84 @@
+package hdfs
+
+import (
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_HTTP_Dir(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+
+ r.NoError(Folder.Create(fs))
+
+ dir, err := fs.Open("/")
+ r.NoError(err)
+ ts := httptest.NewServer(http.FileServer(dir))
+ defer ts.Close()
+
+ res, err := http.Get(ts.URL + "/")
+ r.NoError(err)
+ r.Equal(200, res.StatusCode)
+
+ b, err := ioutil.ReadAll(res.Body)
+ r.NoError(err)
+ r.Contains(string(b), `/public/images/mark.png`)
+}
+
+func Test_HTTP_File_Memory(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+ r.NoError(Folder.Create(fs))
+
+ dir, err := fs.Open("/")
+ r.NoError(err)
+ ts := httptest.NewServer(http.FileServer(dir))
+ defer ts.Close()
+
+ res, err := http.Get(ts.URL + "/public/images/mark.png")
+ r.NoError(err)
+ r.Equal(200, res.StatusCode)
+
+ b, err := ioutil.ReadAll(res.Body)
+ r.NoError(err)
+ r.Contains(string(b), `!/public/images/mark.png`)
+}
+
+func Test_HTTP_Dir_Memory_StripPrefix(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+ r.NoError(Folder.Create(fs))
+
+ dir, err := fs.Open("/public")
+ r.NoError(err)
+ defer dir.Close()
+
+ ts := httptest.NewServer(http.StripPrefix("/assets/", http.FileServer(dir)))
+ defer ts.Close()
+
+ res, err := http.Get(ts.URL + "/assets/images/mark.png")
+ r.NoError(err)
+ r.Equal(200, res.StatusCode)
+
+ b, _ := ioutil.ReadAll(res.Body)
+ // r.NoError(err)
+ r.Contains(string(b), "!/public/images/mark.png")
+
+ res, err = http.Get(ts.URL + "/assets/images/")
+ r.NoError(err)
+ r.Equal(200, res.StatusCode)
+
+ b, _ = ioutil.ReadAll(res.Body)
+ // r.NoError(err)
+ r.Contains(string(b), `/mark.png`)
+ r.NotContains(string(b), `/public`)
+ r.NotContains(string(b), `/images`)
+ r.NotContains(string(b), `/go.mod`)
+}
diff --git a/fs/hdfs/i.exist b/fs/hdfs/i.exist
new file mode 100644
index 0000000..e69de29
diff --git a/fs/hdfs/json_test.go b/fs/hdfs/json_test.go
new file mode 100644
index 0000000..e569706
--- /dev/null
+++ b/fs/hdfs/json_test.go
@@ -0,0 +1,44 @@
+package hdfs
+
+import (
+ "encoding/json"
+ "io"
+ "io/ioutil"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_File_JSON(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+
+ f, err := fs.Create("/radio.radio")
+ r.NoError(err)
+ _, err = io.Copy(f, strings.NewReader(radio))
+ r.NoError(err)
+ r.NoError(f.Close())
+
+ f, err = fs.Open("/radio.radio")
+ r.NoError(err)
+ bi, err := f.Stat()
+ r.NoError(err)
+
+ mj, err := json.Marshal(f)
+ r.NoError(err)
+
+ f2 := &File{}
+
+ r.NoError(json.Unmarshal(mj, f2))
+
+ ai, err := f2.Stat()
+ r.NoError(err)
+
+ r.Equal(bi.Size(), ai.Size())
+
+ fd, err := ioutil.ReadAll(f2)
+ r.NoError(err)
+ r.Equal(radio, string(fd))
+}
diff --git a/fs/hdfs/mkdirall_test.go b/fs/hdfs/mkdirall_test.go
new file mode 100644
index 0000000..2506dfc
--- /dev/null
+++ b/fs/hdfs/mkdirall_test.go
@@ -0,0 +1,23 @@
+package hdfs
+
+import (
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_MkdirAll(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+ err := fs.MkdirAll("/foo/bar/baz", 0755)
+ r.NoError(err)
+
+ fi, err := fs.Stat("/foo/bar/baz")
+ r.NoError(err)
+
+ r.Equal("/foo/bar/baz", fi.Name())
+ r.Equal(os.FileMode(0755), fi.Mode())
+ r.True(fi.IsDir())
+}
diff --git a/fs/hdfs/open_test.go b/fs/hdfs/open_test.go
new file mode 100644
index 0000000..2805825
--- /dev/null
+++ b/fs/hdfs/open_test.go
@@ -0,0 +1,32 @@
+package hdfs
+
+import (
+ "io"
+ "io/ioutil"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_Open(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+
+ _, err := fs.Open("/i.dont.exist")
+ r.Error(err)
+
+ f, err := fs.Create("/i.exist")
+ r.NoError(err)
+ _, err = io.Copy(f, strings.NewReader(radio))
+ r.NoError(err)
+ r.NoError(f.Close())
+
+ f, err = fs.Open("/i.exist")
+ r.NoError(err)
+ b, err := ioutil.ReadAll(f)
+ r.NoError(err)
+ r.NoError(f.Close())
+ r.Equal([]byte(radio), b)
+}
diff --git a/fs/hdfs/radio.radio b/fs/hdfs/radio.radio
new file mode 100644
index 0000000..e69de29
diff --git a/fs/hdfs/stat_test.go b/fs/hdfs/stat_test.go
new file mode 100644
index 0000000..3ce3eaf
--- /dev/null
+++ b/fs/hdfs/stat_test.go
@@ -0,0 +1,24 @@
+package hdfs
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_Stat(t *testing.T) {
+ r := require.New(t)
+
+ fs := NewFS()
+
+ _, err := fs.Stat("/i.dont.exist")
+ r.Error(err)
+
+ f, err := fs.Create("/i.exist")
+ r.NoError(err)
+ r.NoError(f.Close())
+
+ fi, err := fs.Stat("/i.exist")
+ r.NoError(err)
+ r.Equal("/i.exist", fi.Name())
+}
diff --git a/fs/hdfs/walk_test.go b/fs/hdfs/walk_test.go
new file mode 100644
index 0000000..61117e2
--- /dev/null
+++ b/fs/hdfs/walk_test.go
@@ -0,0 +1,67 @@
+package hdfs
+
+import (
+ "io"
+ "os"
+ "sort"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_Walk(t *testing.T) {
+ r := require.New(t)
+
+ files := []struct {
+ name string
+ body string
+ }{
+ {name: "/a/a.txt", body: "A"},
+ {name: "/a/a.md", body: "Amd"},
+ {name: "/b/c/d.txt", body: "B"},
+ {name: "/f.txt", body: "F"},
+ }
+
+ sort.Slice(files, func(a, b int) bool {
+ return files[a].name < files[b].name
+ })
+
+ fs := NewFS()
+
+ for _, file := range files {
+ f, err := fs.Create(file.name)
+ r.NoError(err)
+ _, err = io.Copy(f, strings.NewReader(file.body))
+ r.NoError(err)
+ r.NoError(f.Close())
+ }
+
+ var found []string
+ err := fs.Walk("/", func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ found = append(found, path)
+ return nil
+ })
+ r.NoError(err)
+
+ expected := []string{":/", ":/a", ":/a/a.md", ":/a/a.txt", ":/b", ":/b/c", ":/b/c/d.txt", ":/f.txt"}
+ r.Equal(expected, found)
+
+ found = []string{}
+ err = fs.Walk("/a/", func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ found = append(found, path)
+ return nil
+ })
+ r.NoError(err)
+
+ expected = []string{":/a/a.md", ":/a/a.txt"}
+ r.Equal(expected, found)
+}