---
title: Program Testing
description: Load and test custom Solana programs with litesvm-go
---

Testing a custom Solana program from Go means loading its compiled `.so` file
into the SVM, setting up the accounts it expects, and sending instructions
against it. `litesvm-go` handles all of this in-process - no validator needed.

## Loading a Program

```go
package mytest

import (
    "os"
    "testing"

    litesvm "github.com/LiteSVM/litesvm-go"
    solana "github.com/gagliardetto/solana-go"
)

func TestLoadProgram(t *testing.T) {
    svm, err := litesvm.New()
    if err != nil {
        t.Fatal(err)
    }
    defer svm.Close()

    // Configure for program testing. Setters are infallible in practice;
    // discarding the return keeps the example readable.
    _ = svm.SetSigverify(false)
    _ = svm.SetBlockhashCheck(false)
    _ = svm.SetSysvars()
    _ = svm.SetBuiltins()

    // Load program from .so file.
    programID := solana.NewWallet().PublicKey()
    soPath := "./target/deploy/my_program.so"

    if _, err := os.Stat(soPath); err != nil {
        t.Skipf("program file not found: %s (build with: cargo build-sbf)", soPath)
    }

    if err := svm.AddProgramFromFile(programID, soPath); err != nil {
        t.Fatal(err)
    }
    t.Logf("program loaded: %s", programID)

    // Verify the program account.
    acct := svm.GetAccount(programID)
    if acct == nil {
        t.Fatal("program account missing after load")
    }
    defer acct.Close()

    t.Logf("  executable: %v", acct.Executable())
    t.Logf("  data size:  %d bytes", len(acct.Data()))
}
```

## Complete Program Test Setup

```go
package mytest

import (
    "os"
    "testing"

    litesvm "github.com/LiteSVM/litesvm-go"
    solana "github.com/gagliardetto/solana-go"
)

func TestProgramEndToEnd(t *testing.T) {
    svm, err := litesvm.New()
    if err != nil {
        t.Fatal(err)
    }
    defer svm.Close()

    // Wide-open config: faster tests, less plumbing. Setters are infallible
    // in practice; discarding the return keeps the example readable.
    _ = svm.SetSigverify(false)
    _ = svm.SetBlockhashCheck(false)
    _ = svm.SetSysvars()
    _ = svm.SetBuiltins()
    _ = svm.SetPrecompiles()
    _ = svm.SetTransactionHistory(100)

    // Load the program.
    programID := solana.NewWallet().PublicKey()
    soPath := "./target/deploy/my_program.so"
    if _, err := os.Stat(soPath); err != nil {
        t.Skipf("missing %s", soPath)
    }
    if err := svm.AddProgramFromFile(programID, soPath); err != nil {
        t.Fatal(err)
    }

    // Seed any data accounts the program expects.
    dataAddr := solana.NewWallet().PublicKey()
    dataPayload := make([]byte, 100)

    rent, err := svm.MinimumBalanceForRentExemption(len(dataPayload))
    if err != nil {
        t.Fatal(err)
    }
    acct, err := litesvm.NewAccount(rent, dataPayload, programID, false, 0)
    if err != nil {
        t.Fatal(err)
    }
    defer acct.Close()

    if err := svm.SetAccount(dataAddr, acct); err != nil {
        t.Fatal(err)
    }

    // Fund a payer that will sign the test instruction.
    priv, err := solana.NewRandomPrivateKey()
    if err != nil {
        t.Fatal(err)
    }
    payer := priv.PublicKey()
    if err := svm.Airdrop(payer, 1_000_000_000); err != nil {
        t.Fatal(err)
    }

    // Build an instruction against the program.
    bh, err := svm.LatestBlockhash()
    if err != nil {
        t.Fatal(err)
    }
    ix := &solana.GenericInstruction{
        ProgID: programID,
        AccountValues: solana.AccountMetaSlice{
            {PublicKey: dataAddr, IsWritable: true, IsSigner: false},
            {PublicKey: payer, IsWritable: true, IsSigner: true},
        },
        DataBytes: []byte{0x01 /* discriminator + args */},
    }

    tx, err := solana.NewTransaction(
        []solana.Instruction{ix},
        bh,
        solana.TransactionPayer(payer),
    )
    if err != nil {
        t.Fatal(err)
    }
    if _, err := tx.Sign(func(k solana.PublicKey) *solana.PrivateKey {
        if k.Equals(payer) {
            return &priv
        }
        return nil
    }); err != nil {
        t.Fatal(err)
    }
    txBytes, err := tx.MarshalBinary()
    if err != nil {
        t.Fatal(err)
    }

    // Execute and inspect.
    out, err := svm.SendLegacyTransaction(txBytes)
    if err != nil {
        t.Fatal(err)
    }
    defer out.Close()

    if !out.IsOk() {
        t.Fatalf("program failed: %s\nlogs: %v", out.Error(), out.Logs())
    }

    t.Logf("logs: %v", out.Logs())
    t.Logf("compute: %d CU", out.ComputeUnits())

    // Verify state changes by reading the data account back.
    updated := svm.GetAccount(dataAddr)
    if updated == nil {
        t.Fatal("data account missing after execution")
    }
    defer updated.Close()
    // Decode `updated.Data()` and assert as needed.
}
```

## Using Default Programs

For tests that need standard programs (SPL Token, Memo, ...):

```go
svm, err := litesvm.New()
if err != nil {
    t.Fatal(err)
}
defer svm.Close()

if err := svm.SetDefaultPrograms(); err != nil { // SPL Token, Memo, etc.
    t.Fatal(err)
}
if err := svm.WithNativeMints(); err != nil {    // wrapped-SOL mint
    t.Fatal(err)
}
```

<Callout type="info">
  Build your Solana program with `cargo build-sbf` to generate the `.so` file.
</Callout>

The pattern is always the same:

1. **Configuration** - enable sysvars, builtins, precompiles as needed.
2. **Program loading** - `AddProgramFromFile` (or `AddProgram` for in-memory
   bytes).
3. **Account setup** - pre-populate data accounts with `SetAccount`.
4. **Sending** - build the instruction with `solana-go`, marshal, submit via
   `SendLegacyTransaction`.
5. **Verification** - read accounts back with `GetAccount` and assert on logs,
   compute units, and post-state.

Once you have this skeleton working, scale up by adding more instructions, more
accounts, and a helper for the boilerplate.
