Skip to content

Commit

Permalink
Final (?) fixes for refactoring and Command protocol with README edits
Browse files Browse the repository at this point in the history
  • Loading branch information
mAdkins committed Jun 25, 2023
1 parent ce5ef7f commit 276cf98
Show file tree
Hide file tree
Showing 11 changed files with 485 additions and 253 deletions.
369 changes: 280 additions & 89 deletions README.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions testdata/file.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-logFile=/tmp/lsp-tester.log
-fileFormat=expand
7 changes: 0 additions & 7 deletions testdata/nexus-file.json

This file was deleted.

1 change: 1 addition & 0 deletions testdata/web.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-webPort=8008
3 changes: 0 additions & 3 deletions tester/data/any_map.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package data

// TODO: Connect this to command line flag?
const maxDisplayLen = 32

type AnyMap map[string]any

func (am AnyMap) HasField(name string) bool {
Expand Down
259 changes: 136 additions & 123 deletions tester/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Mode uint
const (
// Client mode pretends to be an LSP to a VSCode client.
// In this mode flags must provide connection data to VSCode.
// The required flag must specify the client connection (e.g. --clientPort).
// The required flag must specify the client connection (e.g. -clientPort).
Client Mode = iota

// Nexus mode uses LSP as server and pretends to be LSP to VSCode client.
Expand All @@ -35,7 +35,7 @@ const (

// Server mode tests the LSP as a server, pretending to be a VSCode client.
// In this mode flags must provide connection data to the LSP.
// The required flag must specify the server connection (e.g. --serverPort).
// The required flag must specify the server connection (e.g. -serverPort).
Server
)

Expand All @@ -52,6 +52,9 @@ const (
TCP
)

// ErrHelp should be visible without drilling into the original flag package.
var ErrHelp = flag.ErrHelp

type Set struct {
*flag.FlagSet
modeChecked bool
Expand All @@ -74,6 +77,8 @@ type Set struct {
logFilePath string
logFileAppend bool
logFileFormat string
logFileLevel zerolog.Level
logFileLvlStr string
logStdFormat string
logMsgTwice bool
version bool
Expand All @@ -84,101 +89,73 @@ func NewSet() *Set {
FlagSet: flag.NewFlagSet("lsp-tester", flag.ContinueOnError),
}
set.StringVar(&set.modeFlag, "mode", "", "Operating modeFlag")
set.StringVar(&set.protocolFlag, "protocol", "", "LSP communication protocolFlag")
set.StringVar(&set.protocolFlag, "protocol", "", "LSP communication protocol")
set.StringVar(&set.hostAddress, "host", "127.0.0.1", "Host address")
set.StringVar(&set.command, "command", "", "LSP server command")
set.UintVar(&set.clientPort, "clientPort", 0, "Port number served for extension to contact")
set.UintVar(&set.serverPort, "serverPort", 0, "Port number on which to contact LSP server")
set.UintVar(&set.webPort, "webPort", 0, "Web port number to enable web access")
set.StringVar(&set.messageDir, "messages", "", "Path to directory of message files")
set.StringVar(&set.requestPath, "request", "", "Path to requestPath file (client modeFlag)")
set.StringVar(&set.requestPath, "request", "", "Path to requestPath file (client mode)")
set.BoolVar(&set.logMsgTwice, "logMsgTwice", false, "Log each message twice with tester in the middle")
set.StringVar(&set.logLevelStr, "logLevel", "info", "Set log level")
set.StringVar(&set.logStdFormat, "logFormat", logging.FmtDefault, "Console output format")
set.StringVar(&set.logFilePath, "logFile", "", "Log file path")
set.UintVar(&set.maxFieldLen, "maxFieldLen", 32, "Maximum length for displayed fields")
set.BoolVar(&set.logFileAppend, "fileAppend", false, "Append to any pre-existing log file")
set.StringVar(&set.logFileFormat, "fileFormat", logging.FmtDefault, "Log file format")
set.UintVar(&set.maxFieldLen, "maxFieldLen", 32, "Maximum length of fields to display")
set.BoolVar(&set.logMsgTwice, "logMsgTwice", false, "Log each message twice with tester in the middle")
set.StringVar(&set.logFileLvlStr, "fileLevel", "info", "Set log file level")
set.BoolVar(&set.version, "version", false, "Show lsp-tester version")
return set
}

var logLevels = map[string]zerolog.Level{
"error": zerolog.ErrorLevel,
"warn": zerolog.WarnLevel,
"info": zerolog.InfoLevel,
"debug": zerolog.DebugLevel,
"trace": zerolog.TraceLevel,
}

func (s *Set) Parse(args []string) error {
var err error
if err = s.FlagSet.Parse(args); err != nil {
return fmt.Errorf("parse flags: %w", err)
}
////////////////////////////////////////////////////////////////////////////////

if err = s.checkMode(); err != nil {
return fmt.Errorf("check --mode: %w", err)
func (s *Set) Validate() error {
if err := s.validateMode(); err != nil {
return fmt.Errorf("check -mode: %w", err)
}

if err = s.checkProtocol(); err != nil {
return fmt.Errorf("check --protocol: %w", err)
if err := s.validateProtocol(); err != nil {
return fmt.Errorf("check -protocol: %w", err)
}

if err = s.checkCommand(); err != nil {
return fmt.Errorf("check --command: %w", err)
if err := s.validateCommand(); err != nil {
return fmt.Errorf("check -command: %w", err)
}

if err = s.fixMessageDirectory(); err != nil {
if err := s.fixMessageDirectory(); err != nil {
return fmt.Errorf("fix message directory: %w", err)
}

if err = s.fixRequestPath(); err != nil {
if err := s.fixRequestPath(); err != nil {
return fmt.Errorf("fix request path: %w", err)
}

if err = s.checkLogging(); err != nil {
return fmt.Errorf("check logging: %w", err)
}

return nil

}

func (s *Set) checkCommand() error {
var err error
if s.command != "" {
parts := regexp.MustCompile("\\s+").Split(s.command, -1)
if s.commandPath, err = exec.LookPath(parts[0]); err != nil {
return fmt.Errorf("get path for command: %w", err)
}
fileInfo, err := os.Stat(s.commandPath)
if err != nil {
return fmt.Errorf("stat %s: %w", s.commandPath, err)
}
mode := fileInfo.Mode()
if !((mode.IsRegular()) || (uint32(mode&fs.ModeSymlink) == 0)) {
return fmt.Errorf("file %s is not normal or a symlink", s.command)
} else if uint32(mode&0111) == 0 {
return fmt.Errorf("file %s is not executable", s.command)
}
s.commandArgs = make([]string, len(parts)-1)
for i, arg := range parts[1:] {
if s.commandArgs[i], err = path.FixHomePath(arg); err != nil {
return fmt.Errorf("fix home path '%s': %w", arg, err)
}
}
}
return nil
var logLevels = map[string]zerolog.Level{
"none": zerolog.Disabled,
"error": zerolog.ErrorLevel,
"warn": zerolog.WarnLevel,
"info": zerolog.InfoLevel,
"debug": zerolog.DebugLevel,
"trace": zerolog.TraceLevel,
}

func (s *Set) checkLogging() error {
func (s *Set) ValidateLogging() error {
if err := s.fixLogFilePath(); err != nil {
return fmt.Errorf("fix log file path: %w", err)
}
var found bool
if s.logLevel, found = logLevels[s.logLevelStr]; !found {
return fmt.Errorf("log level '%s' does not exist", s.logLevelStr)
}
if s.logFileLevel, found = logLevels[s.logFileLvlStr]; !found {
return fmt.Errorf("log file level '%s' does not exist", s.logFileLvlStr)
}
formatErrors := make([]error, 0, 2)
if !logging.IsFormat(s.logStdFormat) {
formatErrors = append(formatErrors, fmt.Errorf("unrecognized -logFormat=%s", s.logStdFormat))
Expand All @@ -192,78 +169,18 @@ func (s *Set) checkLogging() error {
return nil
}

func (s *Set) checkMode() error {
var err error
if s.modeFlag == "" {
// Try to guess mode from other flags.
if s.serverPort != 0 && s.clientPort != 0 {
s.mode = Nexus
} else if s.clientPort != 0 {
s.mode = Client
} else if s.serverPort != 0 {
s.mode = Server
} else {
return errors.New("can't guess -mode")
}
} else if s.mode, err = ModeString(s.modeFlag); err != nil {
return fmt.Errorf("parse -mode flag '%s': %w", s.modeFlag, err)
}
s.modeChecked = true
return nil
}
////////////////////////////////////////////////////////////////////////////////

func (s *Set) checkProtocol() error {
if !s.modeChecked {
return fmt.Errorf("checkMode() must be called before checkProtocol()")
}
var err error
if s.protocolFlag == "" {
// Try to guess the protocol from other flags.
if s.command != "" {
s.protocol = Sub
} else if s.serverPort != 0 || s.clientPort != 0 {
s.protocol = TCP
} else {
return errors.New("can't guess -protocol")
}
} else if s.protocol, err = ProtocolString(s.protocolFlag); err != nil {
return fmt.Errorf("parse -protocol flag '%s': %w", s.protocolFlag, err)
}
switch s.protocol {
case Sub:
if s.ServerConnection() && !s.HasCommand() {
return fmt.Errorf("no -command for Sub/%s", s.Mode())
}
if s.ClientPort() != 0 {
log.Warn().Msg("-clientPort will be ignored in Sub protocol")
}
if s.ServerPort() != 0 {
log.Warn().Msg("-serverPort will be ignored in Sub protocol")
}
case TCP:
if s.ClientConnection() && s.ClientPort() == 0 {
return fmt.Errorf("no -clientPort for TCP/%s", s.Mode())
}
if s.ServerConnection() && s.ServerPort() == 0 {
return fmt.Errorf("no -serverPort for TCP/%s", s.Mode())
}
if s.HasCommand() {
log.Warn().Msg("-command will be ignored in TCP Protocol")
}
}
return nil
}

func (s *Set) ClientConnection() bool {
return s.mode == Client || s.mode == Nexus
func (s *Set) Mode() Mode {
return s.mode
}

func (s *Set) ServerConnection() bool {
func (s *Set) ModeConnectsToClient() bool {
return s.mode == Nexus || s.mode == Server
}

func (s *Set) Mode() Mode {
return s.mode
func (s *Set) ModeConnectsToServer() bool {
return s.mode == Client || s.mode == Nexus
}

func (s *Set) Protocol() Protocol {
Expand Down Expand Up @@ -310,6 +227,10 @@ func (s *Set) LogLevel() zerolog.Level {
return s.logLevel
}

func (s *Set) LogFileLevel() zerolog.Level {
return s.logFileLevel
}

func (s *Set) MaxFieldDisplayLength() int {
return int(s.maxFieldLen)
}
Expand Down Expand Up @@ -408,3 +329,95 @@ func (s *Set) fixRequestPath() error {
func (s *Set) Version() bool {
return s.version
}

////////////////////////////////////////////////////////////////////////////////

func (s *Set) validateCommand() error {
var err error
if s.command != "" {
// TODO: Should be able to handle absolute and relative paths as well.
parts := regexp.MustCompile("\\s+").Split(s.command, -1)
if s.commandPath, err = exec.LookPath(parts[0]); err != nil {
return fmt.Errorf("get path for command: %w", err)
}
fileInfo, err := os.Stat(s.commandPath)
if err != nil {
return fmt.Errorf("stat %s: %w", s.commandPath, err)
}
mode := fileInfo.Mode()
if !((mode.IsRegular()) || (uint32(mode&fs.ModeSymlink) == 0)) {
return fmt.Errorf("file %s is not normal or a symlink", s.command)
} else if uint32(mode&0111) == 0 {
return fmt.Errorf("file %s is not executable", s.command)
}
s.commandArgs = make([]string, len(parts)-1)
for i, arg := range parts[1:] {
if s.commandArgs[i], err = path.FixHomePath(arg); err != nil {
return fmt.Errorf("fix home path '%s': %w", arg, err)
}
}
}
return nil
}

func (s *Set) validateMode() error {
var err error
if s.modeFlag == "" {
// Try to guess mode from other flags.
if s.serverPort != 0 && s.clientPort != 0 {
s.mode = Nexus
} else if s.serverPort != 0 {
s.mode = Client
} else if s.clientPort != 0 {
s.mode = Server
} else {
return errors.New("can't guess -mode")
}
} else if s.mode, err = ModeString(s.modeFlag); err != nil {
return fmt.Errorf("parse -mode flag '%s': %w", s.modeFlag, err)
}
s.modeChecked = true
return nil
}

func (s *Set) validateProtocol() error {
if !s.modeChecked {
return fmt.Errorf("validateMode() must be called before validateProtocol()")
}
var err error
if s.protocolFlag == "" {
// Try to guess the protocol from other flags.
if s.command != "" {
s.protocol = Sub
} else if s.serverPort != 0 || s.clientPort != 0 {
s.protocol = TCP
} else {
return errors.New("can't guess -protocol")
}
} else if s.protocol, err = ProtocolString(s.protocolFlag); err != nil {
return fmt.Errorf("parse -protocol flag '%s': %w", s.protocolFlag, err)
}
switch s.protocol {
case Sub:
if s.ModeConnectsToServer() && !s.HasCommand() {
return fmt.Errorf("no -command for Sub/%s", s.Mode())
}
if s.ClientPort() != 0 {
log.Warn().Msg("-clientPort will be ignored in Sub protocol")
}
if s.ServerPort() != 0 {
log.Warn().Msg("-serverPort will be ignored in Sub protocol")
}
case TCP:
if s.ModeConnectsToClient() && s.ClientPort() == 0 {
return fmt.Errorf("no -clientPort for TCP/%s", s.Mode())
}
if s.ModeConnectsToServer() && s.ServerPort() == 0 {
return fmt.Errorf("no -serverPort for TCP/%s", s.Mode())
}
if s.HasCommand() {
log.Warn().Msg("-command will be ignored in TCP Protocol")
}
}
return nil
}
Loading

0 comments on commit 276cf98

Please sign in to comment.