| package dkms | |
| import ( | |
| "os" | |
| "path" | |
| "path/filepath" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| ) | |
| func TestFindKernelModules(t *testing.T) { | |
| installDir := t.TempDir() | |
| paths := []string{ | |
| "a.ko", | |
| "b.txt", | |
| "x/c.ko", | |
| "x/d.ko", | |
| "x/e.txt", | |
| "x/y/f.ko", | |
| "x/y/g.txt", | |
| } | |
| for _, p := range paths { | |
| installPath := path.Join(installDir, p) | |
| if err := os.MkdirAll(filepath.Dir(installPath), 0777); err != nil { | |
| t.Fatalf("%v", err) | |
| } | |
| if _, err := os.Create(path.Join(installDir, p)); err != nil { | |
| t.Fatalf("%v", err) | |
| } | |
| } | |
| kernelModules, err := FindKernelModules(installDir) | |
| if err != nil { | |
| t.Fatalf("could not find kernel modules: %v", err) | |
| } | |
| expected := map[string]string{ | |
| "a.ko": path.Join(installDir, "a.ko"), | |
| "c.ko": path.Join(installDir, "x/c.ko"), | |
| "d.ko": path.Join(installDir, "x/d.ko"), | |
| "f.ko": path.Join(installDir, "x/y/f.ko"), | |
| } | |
| diff := cmp.Diff(expected, kernelModules) | |
| if diff != "" { | |
| t.Errorf("found kernel modules did not match expected:\ndiff: %s", diff) | |
| } | |
| } | |
| func TestModuleDependencies(t *testing.T) { | |
| deps, err := ModuleDependencies("ext4") | |
| if err != nil { | |
| t.Fatalf("error getting dependencies for module ext4: %v", err) | |
| } | |
| if len(deps) == 0 { | |
| t.Fatalf("expected to find at least one dependency for module ext4; found none") | |
| } | |
| t.Logf("ext4 dependencies: %v", deps) | |
| } | |
| func TestModuleDependencyGraph(t *testing.T) { | |
| insertedModules, err := InsertedModules() | |
| if err != nil { | |
| t.Fatalf("failed to get inserted modules: %v", err) | |
| } | |
| var moduleNames []string | |
| for module := range insertedModules { | |
| moduleNames = append(moduleNames, module) | |
| } | |
| // Technically, this wouldn't be fully correct if there are multiple kernels | |
| // installed, but any nontrivial dependency graph suffices for this test. | |
| modulePaths, err := FindKernelModules("/lib/modules/") | |
| if err != nil { | |
| t.Fatalf("failed to get kernel module paths") | |
| } | |
| graph, err := ModuleDependencyGraph(moduleNames, modulePaths) | |
| if err != nil { | |
| t.Fatalf("failed to build module dependency graph: %v", err) | |
| } | |
| any_non_empty_deps := false | |
| for _, deps := range graph { | |
| if len(deps) > 0 { | |
| any_non_empty_deps = true | |
| } | |
| } | |
| t.Logf("module dependency graph: %v", graph) | |
| if !any_non_empty_deps { | |
| t.Fatalf("expected to find at least one module with dependencies; found none") | |
| } | |
| } | |
| func TestModuleInsertionOrder(t *testing.T) { | |
| testCases := []struct { | |
| modules []string | |
| graph map[string][]string | |
| expected []string | |
| expectedErr bool | |
| }{ | |
| { | |
| modules: []string{"a", "b", "c", "d"}, | |
| graph: map[string][]string{ | |
| "a": {}, | |
| "b": {"d"}, | |
| "c": {"b", "a"}, | |
| "d": {}, | |
| }, | |
| expected: []string{"a", "d", "b", "c"}, | |
| }, | |
| // This test case is the Lustre kernel module dependency graph, which has | |
| // a non-trivial structure and important tie-breaking. | |
| { | |
| modules: []string{ | |
| "ksocklnd", | |
| "in-kernel-ko2iblnd", | |
| "ec", | |
| "fid", | |
| "fld", | |
| "libcfs", | |
| "lmv", | |
| "lnet", | |
| "lov", | |
| "lustre", | |
| "mdc", | |
| "mgc", | |
| "obdclass", | |
| "obdecho", | |
| "osc", | |
| "ptlrpc", | |
| }, | |
| graph: map[string][]string{ | |
| "ec": {}, | |
| "fid": {"obdclass", "ptlrpc", "libcfs"}, | |
| "fld": {"obdclass", "ptlrpc", "libcfs"}, | |
| "in-kernel-ko2iblnd": {"ib_core", "rdma_cm", "libcfs", "lnet"}, | |
| "ksocklnd": {"lnet", "libcfs"}, | |
| "libcfs": {}, | |
| "lmv": {"fld", "obdclass", "fid", "ptlrpc", "libcfs", "lnet"}, | |
| "lnet": {"sunrpc", "libcfs"}, | |
| "lov": {"obdclass", "ptlrpc", "fld", "libcfs"}, | |
| "lustre": {"lnet", "obdclass", "ptlrpc", "libcfs", "lov", "mdc", "lmv", "fid"}, | |
| "mdc": {"ptlrpc", "libcfs", "osc", "obdclass", "lov", "fid"}, | |
| "mgc": {"ptlrpc", "obdclass", "libcfs", "lnet"}, | |
| "obdclass": {"lnet", "libcfs"}, | |
| "obdecho": {"obdclass", "libcfs"}, | |
| "osc": {"libcfs", "obdclass", "ptlrpc", "lnet"}, | |
| "ptlrpc": {"lnet", "libcfs", "obdclass"}, | |
| }, | |
| expected: []string{ | |
| "libcfs", | |
| "sunrpc", | |
| "lnet", | |
| "ksocklnd", | |
| "ib_core", | |
| "rdma_cm", | |
| "in-kernel-ko2iblnd", | |
| "ec", | |
| "obdclass", | |
| "ptlrpc", | |
| "fid", | |
| "fld", | |
| "lmv", | |
| "lov", | |
| "osc", | |
| "mdc", | |
| "lustre", | |
| "mgc", | |
| "obdecho", | |
| }, | |
| }, | |
| { | |
| modules: []string{"nfs"}, | |
| graph: map[string][]string{ | |
| "nfs": {"nfs"}, | |
| }, | |
| expected: nil, | |
| expectedErr: true, | |
| }, | |
| { | |
| modules: []string{ | |
| "sunrpc", | |
| "auth_rpcgss", | |
| "rpcsec_gss_krb5", | |
| "rpcrdma", | |
| "nfsv4", | |
| "nfs", | |
| "nfs_layout_nfsv41_files", | |
| "blocklayoutdriver", | |
| "nfs_layout_flexfiles", | |
| "nfsd", | |
| "nfs_acl", | |
| "compat_nfs_ssc", | |
| "lockd", | |
| }, | |
| // Nontrivial graph with a circular dependency: | |
| // ( nfs -> lockd -> grace -> blocklayoutdriver -> nfs ) | |
| graph: map[string][]string{ | |
| "sunrpc": {}, | |
| "auth_rpcgss": {"sunrpc"}, | |
| "rpcsec_gss_krb5": {"sunrpc", "auth_rpcgss"}, | |
| "rpcrdma": {"sunrpc", "rdma_cm", "ib_core"}, | |
| "nfs": {"sunrpc", "lockd"}, | |
| "nfsv4": {"nfs", "sunrpc", "dns_resolver"}, | |
| "nfs_layout_nfsv41_files": {"nfsv4", "nfs", "sunrpc"}, | |
| "blocklayoutdriver": {"nfsv4", "nfs", "sunrpc"}, | |
| "nfs_layout_flexfiles": {"nfsv4", "nfs", "sunrpc"}, | |
| "nfsd": {"sunrpc", "lockd", "nfs_acl", "grace", "auth_rpcgss"}, | |
| "nfs_acl": {"sunrpc"}, | |
| "compat_nfs_ssc": {}, | |
| "lockd": {"grace", "sunrpc"}, | |
| "grace": {"blocklayoutdriver"}, | |
| }, | |
| expected: nil, | |
| expectedErr: true, | |
| }, | |
| } | |
| for _, testCase := range testCases { | |
| order, err := ModuleInsertionOrder(testCase.modules, testCase.graph) | |
| if err != nil { | |
| if testCase.expectedErr { | |
| t.Logf("got expected error: %v", err) | |
| } else { | |
| t.Errorf("got unexpected error: %v", err) | |
| } | |
| } else if testCase.expectedErr { | |
| t.Errorf("expected test case to error, but didn't") | |
| } | |
| diff := cmp.Diff(order, testCase.expected) | |
| if len(diff) > 0 { | |
| t.Fatalf("order differs from expected: %s", diff) | |
| } | |
| } | |
| } |