Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ jbang.egg-info
jbang/__pycache__
__pycache__
.coverage
.venv
4 changes: 2 additions & 2 deletions .justfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
test:
source .venv/bin/activate
.venv/bin/activate
#pip3 install -e .
uv pip install -e ".[test]"

echo Running tests with no jbang in PATH
PATH=$(echo $PATH | tr ':' '\n' | grep -v "\.jbang/bin" | tr '\n' ':' | sed 's/:$//') .venv/bin/python -m pytest -o log_cli_level=DEBUG -o log_cli=true

release:
source .venv/bin/activate
.venv/bin/activate
uv pip install setuptools
gh release create `python3 setup.py --version` --generate-notes
4 changes: 2 additions & 2 deletions jbang/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .jbang import exec, spawnSync, main, quote
from .jbang import exec, spawnSync, main, quote, popen

__all__ = ['exec', 'spawnSync', 'main', 'quote']
__all__ = ['exec', 'spawnSync', 'main', 'quote', 'popen']

if __name__ == "__main__":
main()
24 changes: 24 additions & 0 deletions jbang/jbang.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,30 @@ def spawnSync(args: Union[str, List[str]]) -> Any:
2
)

def popen(args: Union[str, List[str]]) -> subprocess.Popen:
"""Returns a reference to a subprocess.Popen instance for streaming stdout."""
log.debug(f"trying to execute popen command: {args}")

cmdLine = _getCommandLine(args)

if not cmdLine:
print("Could not locate a way to run jbang. Try installing jbang manually and try again.")
raise Exception(
"Could not locate a way to run jbang. Try installing jbang manually and try again.",
2
)

return subprocess.Popen(
cmdLine,
shell=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True,
)

def main():
"""Command-line entry point for jbang-python."""
log.debug("Starting jbang-python CLI")
Expand Down
55 changes: 55 additions & 0 deletions tests/test_jbang.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,48 @@
import sys

from pathlib import Path
import pytest

import jbang
from jbang.jbang import CommandResult

@pytest.fixture
def java_hello(tmp_path: Path):
code = """
public class HelloWorld {
public static void main(String[] args) {
System.out.print("Hello, world!");
}
}
"""

file_path = tmp_path / "HelloWorld.java"
file_path.write_text(code.strip())
return file_path

@pytest.fixture
def streamable_java_hello(tmp_path: Path):
code = """
import java.io.*;
import java.util.Random;

public class HelloStreamable {
static final BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out), 1 << 20);
public static void main(String[] args) throws Exception {
String message = \"Hello, world!\";
Random rand = new Random();
for (int i = 0; i < message.length(); i++){
out.write(message.charAt(i));
out.newLine();
out.flush();
Thread.sleep(rand.nextInt(3));
}
}
}
"""
file_path = tmp_path / "HelloStreamable.java"
file_path.write_text(code.strip())
return file_path

def test_version_command():
"""Test version command."""
Expand Down Expand Up @@ -60,6 +98,23 @@ def test_invalid_java_version():
out = jbang.exec('--java invalid properties@jbangdev java.version')
assert 'Invalid version' in out.stderr

def test_popen_single_write(java_hello):
process = jbang.popen(str(java_hello))
process.wait()

assert process.returncode == 0
assert process.stdout.readlines()[0] == "Hello, world!"

def test_popen_streamable_java_hello(streamable_java_hello):
process = jbang.popen(str(streamable_java_hello))
rows = []
message = "Hello, world!"
for i, line in enumerate(process.stdout):
assert line.rstrip("\n") == message[i]

return_code = process.wait()
assert return_code == 0

@pytest.mark.skipif(sys.platform == 'win32', reason="Quote tests behave differently on Windows")
class TestQuoting:
def test_quote_empty_string(self):
Expand Down