Using Port to drive interactive command line program

I have a third party program that takes a password in on the command line interactively. I am trying to drive an integration test. I have tried running the program with Port and sending like so:

send(port, {self(), {:command, "thisisthepassword"}})

But I get inappropriate ioctl for device

Does anyone have any suggestions on how to fix this?

I’m not super familiar with sending stdin to an OS process that you are communicating with via a port. But I have been playing with the erlexec library recently which supports this scenario easier. It would look something like this (untested):

{:ok, pid, os_pid} = :exec.run_link("initial_command --with args", [])
:exec.send(os_pid, "thisisthepassword")

Docs:

How have you opened the port?

Port.open({:spawn_executable, state[:lncli_path]}, [:binary, args: global_options ++ args])

I have tried both with and without the :binary option set.

My assumption is, that the program does not actually read the password from stdin, but uses some hooks to intercept keypresses.

Can you make it work by doing echo "thisisthepassword" | yourprogramm from a shell? If not, then you won’t be able to use it as a port… Or you need to find a way to provide the secrets via other means, config files, key files, whatever your tool supports…

1 Like

I found a hacky solution that works. Install the debian expect package which gives the unbuffer program which allows you to fake an interactive TTY. Then you can open the port and send in the password like so:

unbuffer = System.find_executable("unbuffer")
port = Port.open({:spawn_executable, unbuffer}, [:binary, args: ["-p"] ++ lncli_cmd])
send(port, {self(), {:command, "thisisthepassword\n"}})

Bummer to have to add a system dependency, but it’s just for integration testing, so it doesn’t add a production dependency.

1 Like