Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix deadlock in errors-must e2e test #874

Merged
merged 5 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
go-version: '1.24'
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.60
version: v1.64
only-new-issues: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ cmd/tmp
/neva-lsp-windows-arm64.exe
dist
trace.log
ir.yml
output
node_modules
__debug*
Expand Down
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/neva",
"cwd": "${workspaceFolder}/e2e/for_with_range_and_if",
"cwd": "${workspaceFolder}/e2e/errors_must",
"args": ["run", "--trace", "main"]
},
{
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/message_passing/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func BenchmarkMessagePassing(b *testing.B) {
// Reset timer after setup
b.ResetTimer()

for i := 0; i < b.N; i++ {
for b.Loop() {
cmd := exec.Command("neva", "run", "message_passing")
out, err := cmd.CombinedOutput()
require.NoError(b, err, string(out))
Expand Down
39 changes: 39 additions & 0 deletions e2e/cli/run_with_ir/e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package test

import (
"os"
"os/exec"
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

func Test(t *testing.T) {
defer func() {
require.NoError(t, os.RemoveAll("src"))
require.NoError(t, os.Remove("ir.yml"))
}()

// Create new project
cmd := exec.Command("neva", "new")
require.NoError(t, cmd.Run())

// Run with IR emission
cmd = exec.Command("neva", "run", "--emit-ir", "src")
out, err := cmd.CombinedOutput()
require.NoError(t, err)
require.Equal(t, "Hello, World!\n", string(out))
require.Equal(t, 0, cmd.ProcessState.ExitCode())

// Verify IR file exists and is valid YAML
irBytes, err := os.ReadFile("ir.yml")
require.NoError(t, err)

var ir struct {
Connections map[string]string `yaml:"connections"`
Funcs []any `yaml:"funcs"`
}
require.NoError(t, yaml.Unmarshal(irBytes, &ir))
require.NotEmpty(t, ir.Funcs)
}
1 change: 1 addition & 0 deletions e2e/cli/run_with_ir/neva.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
neva: 0.31.0
21 changes: 12 additions & 9 deletions e2e/errors_must/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ import (
)

func Test(t *testing.T) {
cmd := exec.Command("neva", "run", "main")
// we run N times to make sure https://github.com/nevalang/neva/issues/872 is fixed
for range 10 {
cmd := exec.Command("neva", "run", "main")

out, err := cmd.CombinedOutput()
require.NoError(t, err)
require.Equal(
t,
"success!\n",
string(out),
)
out, err := cmd.CombinedOutput()
require.NoError(t, err)
require.Equal(
t,
"success!\n",
string(out),
)

require.Equal(t, 0, cmd.ProcessState.ExitCode())
require.Equal(t, 0, cmd.ProcessState.ExitCode())
}
}
8 changes: 5 additions & 3 deletions examples/delayed_echo/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
)

func Test(t *testing.T) {
// for i := 0; i < 10; i++ {
err := os.Chdir("..")
require.NoError(t, err)

Expand All @@ -23,18 +24,18 @@ func Test(t *testing.T) {
start := time.Now()
out, err := cmd.CombinedOutput()
elapsed := time.Since(start)
require.NoError(t, err)
require.NoError(t, err, string(out))

// Check execution time is between 1-5 seconds
require.GreaterOrEqual(t, elapsed.Seconds(), 1.0)
require.LessOrEqual(t, elapsed.Seconds(), 5.0)

// Split output into lines and verify contents
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
require.Equal(t, 7, len(lines)) // Hello + World + 5 numbers
require.Equal(t, 7, len(lines), string(out)) // Hello + World + 5 numbers

// First line must be Hello
require.Equal(t, "Hello", lines[0])
require.Equal(t, "Hello", lines[0], string(out))

// Create set of expected remaining values
expected := map[string]bool{
Expand All @@ -59,4 +60,5 @@ func Test(t *testing.T) {
}

require.Equal(t, 0, cmd.ProcessState.ExitCode())
// }
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/nevalang/neva

go 1.23
go 1.24

require (
github.com/Masterminds/semver/v3 v3.2.1
Expand Down
11 changes: 7 additions & 4 deletions internal/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func newRunCmd(workdir string, nativec compiler.Compiler) *cli.Command {
Name: "trace",
Usage: "Write trace information to file",
},
&cli.BoolFlag{
Name: "emit-ir",
Usage: "Emit intermediate representation to ir.yml file",
},
},
ArgsUsage: "Provide path to main package",
Action: func(cliCtx *cli.Context) error {
Expand All @@ -30,10 +34,8 @@ func newRunCmd(workdir string, nativec compiler.Compiler) *cli.Command {
return err
}

var trace bool
if cliCtx.IsSet("trace") {
trace = true
}
trace := cliCtx.IsSet("trace")
emitIR := cliCtx.IsSet("emit-ir")

// we need to always set GOOS for compiler backend
prevGOOS := os.Getenv("GOOS")
Expand All @@ -55,6 +57,7 @@ func newRunCmd(workdir string, nativec compiler.Compiler) *cli.Command {
Main: mainPkg,
Output: workdir,
Trace: trace,
EmitIR: emitIR,
}

if err := nativec.Compile(cliCtx.Context, input); err != nil {
Expand Down
23 changes: 23 additions & 0 deletions internal/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ package compiler
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"gopkg.in/yaml.v3"

"github.com/nevalang/neva/internal/compiler/ir"
"github.com/nevalang/neva/internal/compiler/sourcecode"
"github.com/nevalang/neva/internal/compiler/sourcecode/core"
Expand All @@ -20,6 +25,7 @@ type CompilerInput struct {
Main string
Output string
Trace bool
EmitIR bool
}

func (c Compiler) Compile(ctx context.Context, input CompilerInput) error {
Expand All @@ -33,9 +39,26 @@ func (c Compiler) Compile(ctx context.Context, input CompilerInput) error {
return err
}

if input.EmitIR {
if err := c.emitIR(input.Output, meResult.IR); err != nil {
return fmt.Errorf("emit IR: %w", err)
}
}

return c.be.Emit(input.Output, meResult.IR, input.Trace)
}

func (c Compiler) emitIR(dst string, prog *ir.Program) error {
path := filepath.Join(dst, "ir.yml")
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
// fmt.Println(dst, path, prog)
return yaml.NewEncoder(f).Encode(prog)
}

type Frontend struct {
builder Builder
parser Parser
Expand Down
3 changes: 1 addition & 2 deletions internal/compiler/desugarer/desugarer.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ type Scope interface {
Entity(ref core.EntityRef) (src.Entity, core.Location, error)
Relocate(location core.Location) src.Scope
Location() *core.Location
GetFirstInportName(nodes map[string]src.Node, portAddr src.PortAddr) (string, error)
GetFirstOutportName(nodes map[string]src.Node, portAddr src.PortAddr) (string, error)
GetNodeIOByPortAddr(nodes map[string]src.Node, portAddr src.PortAddr) (src.IO, error)
}

func (d *Desugarer) desugarPkg(pkg src.Package, scope Scope) (src.Package, error) {
Expand Down
29 changes: 7 additions & 22 deletions internal/compiler/desugarer/mocks_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 40 additions & 3 deletions internal/compiler/desugarer/network.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package desugarer

import (
"errors"
"fmt"

"github.com/nevalang/neva/internal/compiler"
Expand Down Expand Up @@ -238,7 +239,7 @@ func (d *Desugarer) desugarSingleReceiver(
}, nil
}

firstInportName, err := scope.GetFirstInportName(nodes, *receiver.PortAddr)
firstInportName, err := d.getFirstInportName(scope, nodes, *receiver.PortAddr)
if err != nil {
return desugarReceiverResult{}, fmt.Errorf("get first inport name: %w", err)
}
Expand Down Expand Up @@ -441,7 +442,7 @@ func (d *Desugarer) desugarChainedConnection(
chainHeadPort = chainHead.PortAddr.Port
if chainHeadPort == "" {
var err error
chainHeadPort, err = scope.GetFirstInportName(nodes, *chainHead.PortAddr)
chainHeadPort, err = d.getFirstInportName(scope, nodes, *chainHead.PortAddr)
if err != nil {
return desugarConnectionResult{}, fmt.Errorf("get first inport name: %w", err)
}
Expand Down Expand Up @@ -688,7 +689,7 @@ func (d *Desugarer) desugarSingleSender(
if sender.PortAddr != nil {
portName := sender.PortAddr.Port
if sender.PortAddr.Port == "" {
firstOutportName, err := scope.GetFirstOutportName(nodes, *sender.PortAddr)
firstOutportName, err := d.getFirstOutportName(scope, nodes, *sender.PortAddr)
if err != nil {
return desugarSenderResult{}, fmt.Errorf("get first outport name: %w", err)
}
Expand Down Expand Up @@ -842,6 +843,42 @@ func (d *Desugarer) desugarSingleSender(
}, nil
}

func (d *Desugarer) getFirstInportName(
scope Scope,
nodes map[string]src.Node,
portAddr src.PortAddr,
) (string, error) {
io, err := scope.GetNodeIOByPortAddr(nodes, portAddr)
if err != nil {
return "", err
}
for inport := range io.In {
return inport, nil
}
return "", errors.New("first inport not found")
}

func (d *Desugarer) getFirstOutportName(
scope Scope,
nodes map[string]src.Node,
portAddr src.PortAddr,
) (string, error) {
io, err := scope.GetNodeIOByPortAddr(nodes, portAddr)
if err != nil {
return "", err
}

// important: skip `err` outport if node has err guard
for outport := range io.Out {
if outport == "err" && nodes[portAddr.Node].ErrGuard {
continue
}
return outport, nil
}

return "", errors.New("first outport not found")
}

var newComponentRef = core.EntityRef{
Pkg: "builtin",
Name: "New",
Expand Down
Loading
Loading