Create a custom fake simple plugin for testing (#60)

Using a real plugin means:
1. we need to be online during testing
2. if the plugin has a different version from rizin
   on the machine often it won't event compile

This commit introduces a fake plugin in the
simpleplugin directory. During testing, an HTTP
server is spawned. The .tar.gz is created and
served on-the-fly to every test that asks for it.
This commit is contained in:
VaiTon authored and GitHub committed 2025-06-14 16:50:19 +08:00
1 parent 49f0d96de7
commit 5aafd7a4f3
7 files changed
+274 -30

No files matched your search

+7 -1
View File
@@ -210,7 +210,7 @@ func (rp RizinPackage) downloadGit(artifactsPath string) error {
// Download the source code of a package and extract it in the provided path
func (rp RizinPackage) Download(baseArtifactsPath string) error {
log.Printf("Downloading package %s...\n", rp.PackageName)
log.Printf("Downloading package %s... from '%s'", rp.PackageName, rp.PackageSource.URL)
artifactsPath := rp.artifactsPath(baseArtifactsPath)
err := os.MkdirAll(artifactsPath, os.FileMode(0755))
if err != nil {
@@ -249,6 +249,12 @@ func (rp RizinPackage) buildMeson(site Site) error {
cmd.Dir = srcPath
cmd.Stdout = log.Writer()
cmd.Stderr = log.Writer()
log.Printf("Running meson setup:")
log.Printf("\tdir: %s", srcPath)
log.Printf("\targs: %s", strings.Join(args, " "))
if err := cmd.Run(); err != nil {
return err
}
+169 -28
View File
@@ -1,10 +1,19 @@
package pkg
import (
"archive/tar"
"compress/gzip"
"context"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
@@ -25,7 +34,7 @@ func TestDownloadSimplePackage(t *testing.T) {
},
}
tmpPath, err := os.MkdirTemp(os.TempDir(), "rzpmtest")
tmpPath, err := os.MkdirTemp(os.TempDir(), "rzpmtest-artifacts")
require.NoError(t, err, "temp path should be created")
defer os.RemoveAll(tmpPath)
@@ -43,7 +52,7 @@ func TestWrongHash(t *testing.T) {
PackageDescription: "simple description",
PackageVersion: "0.0.1",
PackageSource: &RizinPackageSource{
URL: "https://github.com/rizinorg/jsdec/archive/refs/tags/v0.7.0.tar.gz",
URL: "https://github.com/rizinorg/jsdec/archive/refs/tags/v0.8.0.tar.gz",
Hash: "sha256:2b2587dd117d48b284695416a7349a21c4dd30fbe75cc5890ed74945c9b474aa",
BuildSystem: "meson",
Directory: "p",
@@ -113,60 +122,192 @@ func (s FakeSite) Remove() error {
return nil
}
func TestInstallSimplePackage(t *testing.T) {
log.SetOutput(os.Stderr)
p := RizinPackage{
// tarGzDir creates a .tar.gz archive at destFile containing the contents of srcDir.
func tarGzDir(destFile, srcDir string) error {
f, err := os.Create(destFile)
if err != nil {
return err
}
defer f.Close()
gw := gzip.NewWriter(f)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return err
}
if relPath == "." {
return nil
}
header, err := tar.FileInfoHeader(info, path)
if err != nil {
return err
}
header.Name = relPath
if err := tw.WriteHeader(header); err != nil {
return err
}
if info.Mode().IsRegular() {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(tw, f)
return err
}
return nil
})
}
func calcFileSha256(t *testing.T, name string) string {
f, err := os.Open(name)
if err != nil {
t.Fatalf("failed to open file: %v", err)
}
defer f.Close()
content, err := io.ReadAll(f)
require.NoError(t, err, "file should be read")
h := sha256.Sum256(content)
return fmt.Sprintf("%x", h)
}
func serveDirAsTarGz(t *testing.T, ctx context.Context, dir string) (url string, hash string, err error) {
tmpFile, err := os.CreateTemp("", "rzpmtest-*.tar.gz")
if err != nil {
return "", "", fmt.Errorf("failed to create temp file: %w", err)
}
err = tarGzDir(tmpFile.Name(), dir)
if err != nil {
return "", "", fmt.Errorf("failed to create tar.gz: %w", err)
}
hash = calcFileSha256(t, tmpFile.Name())
// Open an HTTP server and serve the file at a random URL
mux := http.NewServeMux()
fileName := filepath.Base(tmpFile.Name())
mux.HandleFunc("/"+fileName, func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, tmpFile.Name())
})
server := &http.Server{
Addr: "127.0.0.1:0",
Handler: mux,
}
ln, err := net.Listen("tcp", server.Addr)
if err != nil {
return "", "", fmt.Errorf("failed to listen: %w", err)
}
go func() {
<-ctx.Done()
server.Shutdown(context.Background())
tmpFile.Close()
}()
go server.Serve(ln)
url = fmt.Sprintf("http://%s/%s", ln.Addr().String(), fileName)
return url, hash, nil
}
func createTestPackage(t *testing.T, ctx context.Context) RizinPackage {
pwd, err := os.Getwd()
require.NoError(t, err, "current working directory should be retrieved")
dir := filepath.Join(filepath.Dir(pwd), "simpleplugin")
require.DirExists(t, dir, "test package directory should exist")
serveURL, hash, err := serveDirAsTarGz(t, ctx, dir)
require.NoError(t, err, "failed to serve test package directory as tar.gz")
simplePackage := RizinPackage{
PackageName: "simple",
PackageDescription: "simple description",
PackageVersion: "0.0.1",
PackageSource: &RizinPackageSource{
URL: "https://github.com/rizinorg/jsdec/archive/refs/tags/v0.7.0.tar.gz",
Hash: "2b2587dd117d48b284695416a7349a21c4dd30fbe75cc5890ed74945c9b474ea",
URL: serveURL,
Hash: hash,
BuildSystem: "meson",
Directory: "jsdec-0.7.0",
Directory: "",
BuildArguments: []string{"-Drizin_plugdir="},
},
}
tmpPath, err := os.MkdirTemp(os.TempDir(), "rzpmtest")
return simplePackage
}
func TestInstallSimplePackage(t *testing.T) {
log.SetOutput(os.Stderr)
p := createTestPackage(t, context.Background())
tmpPath, err := os.MkdirTemp(os.TempDir(), "rzpmtest-build")
require.NoError(t, err, "temp path should be created")
fmt.Printf("Temporary path for build: %s\n", tmpPath)
defer os.RemoveAll(tmpPath)
pluginsPath, err := os.MkdirTemp(os.TempDir(), "rzpmtest-install")
require.NoError(t, err, "install path should be created")
fmt.Printf("Temporary path for plugins: %s\n", pluginsPath)
defer os.RemoveAll(pluginsPath)
p.PackageSource.BuildArguments[0] += pluginsPath
err = p.Download(tmpPath)
require.NoError(t, err, "package should be downloaded")
installed_files, err := p.Install(FakeSite{ArtifactsDir: tmpPath})
assert.NoError(t, err, "The plugin should be built and installed without errors")
files, err := ioutil.ReadDir(pluginsPath)
installedFiles, err := p.Install(FakeSite{ArtifactsDir: tmpPath})
require.NoError(t, err, "The plugin should be built and installed without errors")
files, err := os.ReadDir(pluginsPath)
fmt.Printf("Installed files: %v\n", installedFiles)
fmt.Printf("Found files in pluginsPath: %v\n", files)
require.NoError(t, err, "pluginsPath should be read")
require.True(t, len(files) >= 1, "there should be one plugin installed")
for i := range files {
assert.Contains(t, files[i].Name(), "core_pdd", "the name of the plugin lib is jsdec")
// check that every file in pluginsPath is in installedFiles
installedFileMap := make(map[string]bool)
for _, file := range installedFiles {
name := filepath.Base(file)
installedFileMap[name] = true
}
for i := range installed_files {
assert.Contains(t, installed_files[i], "core_pdd", "jsdec should install core_pdd in plugins dir")
for _, file := range files {
assert.Contains(t, installedFileMap, file.Name(), "installed files should match the files in pluginsPath")
}
file := files[0]
if runtime.GOOS == "windows" {
assert.Contains(t, file.Name(), "plugin.dll", "the name of the plugin should contain 'plugin.dll'")
} else if runtime.GOOS == "darwin" {
assert.Contains(t, file.Name(), "libplugin.dylib", "the name of the plugin should contain 'plugin.dylib'")
} else {
assert.Contains(t, file.Name(), "libplugin.so", "the name of the plugin should contain 'plugin.so'")
}
for _, file := range files {
assert.Contains(t, file.Name(), "plugin", "the name of the plugin should contain 'plugin'")
}
for _, file := range installedFiles {
assert.Contains(t, file, "plugin", "the name of the plugin should contain 'plugin'")
}
}
func TestUninstallSimplePackage(t *testing.T) {
log.SetOutput(os.Stderr)
p := RizinPackage{
PackageName: "simple",
PackageDescription: "simple description",
PackageVersion: "0.0.1",
PackageSource: &RizinPackageSource{
URL: "https://github.com/rizinorg/jsdec/archive/refs/tags/v0.7.0.tar.gz",
Hash: "2b2587dd117d48b284695416a7349a21c4dd30fbe75cc5890ed74945c9b474ea",
BuildSystem: "meson",
Directory: "jsdec-0.7.0",
BuildArguments: []string{"-Drizin_plugdir="},
},
}
p := createTestPackage(t, context.Background())
tmpPath, err := os.MkdirTemp(os.TempDir(), "rzpmtest")
require.NoError(t, err, "temp path should be created")
+11 -1
View File
@@ -4,5 +4,15 @@ import "strings"
func GetMajorMinorVersion(version string) string {
splits := strings.SplitN(version, ".", 3)
return splits[0] + "." + splits[1]
var major, minor string
major = splits[0]
if len(splits) < 2 {
// If there is no minor version, return the major version with a ".0"
minor = "0"
} else {
minor = splits[1]
}
return major + "." + minor
}
+25
View File
@@ -0,0 +1,25 @@
package pkg
import "testing"
func TestGetMajorMinorVersion(t *testing.T) {
tests := []struct {
version string
want string
}{
{"1.2.3", "1.2"},
{"10.20.30", "10.20"},
{"0.1.2", "0.1"},
{"5.6", "5.6"}, // Edge case with no patch version
{"1", "1.0"}, // Edge case with only major version
}
for _, tt := range tests {
t.Run(tt.version, func(t *testing.T) {
got := GetMajorMinorVersion(tt.version)
if got != tt.want {
t.Errorf("GetMajorMinorVersion(%q) = %q; want %q", tt.version, got, tt.want)
}
})
}
}
+13
View File
@@ -0,0 +1,13 @@
project('simple_project', 'c')
rz_core_dep = dependency('rz_core')
plugins_dir = get_option('rizin_plugdir')
message('Plugins install directory: ' + plugins_dir)
library(
'plugin',
['plugin.c'],
dependencies: [rz_core_dep],
install: true,
install_dir: plugins_dir,
)
+5
View File
@@ -0,0 +1,5 @@
option(
'rizin_plugdir',
type: 'string',
description: 'Rizin plugin directory',
)
+44
View File
@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2018-2023 Giovanni Dante Grazioli <[email protected]>
// SPDX-FileCopyrightText: 2025 Eyad Issa <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-or-later
#include <rz_analysis.h>
#include <rz_cmd.h>
#include <rz_cons.h>
#include <rz_core.h>
#include <rz_lib.h>
#include <rz_types.h>
static bool rz_cmd_init(RzCore *core) {
// no-op plugin, does nothing
RZ_LOG_INFO("Initializing simple plugin\n");
return true;
}
static bool rz_cmd_fini(RzCore *core) {
// no-op plugin, does nothing
RZ_LOG_INFO("Finalizing simple plugin\n");
return true;
}
RzCorePlugin rz_core_plugin_example = {
.name = "simple-plugin",
.desc = "A simple no-op plugin for Rizin",
.license = "BSD-3-Clause",
.init = rz_cmd_init,
.fini = rz_cmd_fini,
};
#ifdef _MSC_VER
#define _RZ_API __declspec(dllexport)
#else
#define _RZ_API __attribute__((visibility("default")))
#endif
#ifndef CORELIB
_RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_CORE,
.data = &rz_core_plugin_example,
.version = RZ_VERSION,
};
#endif