Skip to content

Commit

Permalink
added unit tests and fixed rest of converters
Browse files Browse the repository at this point in the history
  • Loading branch information
jbolor21 committed May 16, 2024
1 parent 84b9347 commit 4d5fa13
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 8 deletions.
9 changes: 9 additions & 0 deletions pyrit/prompt_converter/add_text_image_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import logging
import pathlib
import base64
import concurrent.futures
import asyncio

from augly.image.transforms import OverlayText
from augly.utils import base_paths
Expand Down Expand Up @@ -52,6 +54,13 @@ def __init__(
self._output_name = output_filename

def convert(self, *, prompt: str, input_type: PromptDataType = "image_path") -> ConverterResult:
"""
Deprecated. Use async_convert instead.
"""
pool = concurrent.futures.ThreadPoolExecutor()
return pool.submit(asyncio.run, self.async_convert(prompt=prompt, input_type=input_type)).result()

async def async_convert(self, *, prompt: str, input_type: PromptDataType = "image_path") -> ConverterResult:
"""
Converter that adds text to an image
Expand Down
20 changes: 15 additions & 5 deletions pyrit/prompt_converter/azure_speech_text_to_audio_converter.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
import logging
from typing import Literal

import azure.cognitiveservices.speech as speechsdk
import concurrent.futures
import asyncio

from typing import Literal
from pyrit.common import default_values
from pyrit.models.data_type_serializer import data_serializer_factory
from pyrit.models.prompt_request_piece import PromptDataType
Expand Down Expand Up @@ -55,9 +56,16 @@ def __init__(
self._output_format = output_format

def input_supported(self, input_type: PromptDataType) -> bool:
return input_type == "text"

def convert(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult:
return input_type == "audio_path"

def convert(self, *, prompt: str, input_type: PromptDataType = "audio_path") -> ConverterResult:
"""
Deprecated. Use async_convert instead.
"""
pool = concurrent.futures.ThreadPoolExecutor()
return pool.submit(asyncio.run, self.async_convert(prompt=prompt, input_type=input_type)).result()

async def async_convert(self, *, prompt: str, input_type: PromptDataType = "audio_path") -> ConverterResult:
if not self.input_supported(input_type):
raise ValueError("Input type not supported")

Expand Down Expand Up @@ -104,3 +112,5 @@ def convert(self, *, prompt: str, input_type: PromptDataType = "text") -> Conver
raise

return ConverterResult(output_text=audio_serializer_file, output_type="audio_path")


14 changes: 12 additions & 2 deletions pyrit/prompt_converter/translation_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import logging
import uuid
import pathlib
import concurrent.futures
import asyncio

from pyrit.models import PromptDataType
from pyrit.models import PromptRequestPiece, PromptRequestResponse
Expand Down Expand Up @@ -45,8 +47,15 @@ def __init__(self, *, converter_target: PromptChatTarget, language: str, prompt_

self.system_prompt = prompt_template.apply_custom_metaprompt_parameters(languages=language)

def convert(self, *, prompt: str, input_type: PromptDataType = "image_type") -> ConverterResult:
"""
Deprecated. Use async_convert instead.
"""
pool = concurrent.futures.ThreadPoolExecutor()
return pool.submit(asyncio.run, self.async_convert(prompt=prompt, input_type=input_type)).result()

@retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
def convert(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult:
async def async_convert(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult:
"""
Generates variations of the input prompts using the converter target.
Parameters:
Expand Down Expand Up @@ -82,7 +91,8 @@ def convert(self, *, prompt: str, input_type: PromptDataType = "text") -> Conver
]
)

response_msg = self.converter_target.send_prompt(prompt_request=request).request_pieces[0].converted_value
response = await self.converter_target.send_prompt_async(prompt_request=request)
response_msg = response.request_pieces[0].converted_value

try:
llm_response: dict[str, str] = json.loads(response_msg)["output"]
Expand Down
182 changes: 181 additions & 1 deletion tests/test_prompt_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def test_base64_prompt_converter() -> None:
assert output.output_text == "dGVzdA=="
assert output.output_type == "text"


def test_rot13_converter_init() -> None:
converter = ROT13Converter()
output = converter.convert(prompt="test", input_type="text")
Expand Down Expand Up @@ -221,3 +220,184 @@ def test_add_text_image_converter() -> None:
assert os.path.exists(converted_image.output_text)
os.remove(converted_image.output_text)
os.remove("test.png")

#### TESTING ASYNC VERSIONS:
@pytest.mark.asyncio
def test_base64_prompt_converter() -> None:
converter = Base64Converter()
output = converter.convert(prompt="test", input_type="text")
assert output.output_text == "dGVzdA=="
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_rot13_converter_init() -> None:
converter = ROT13Converter()
output = await converter.async_convert(prompt="test", input_type="text")
assert output.output_text == "grfg"
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_unicode_sub_default_prompt_converter() -> None:
converter = UnicodeSubstitutionConverter()
output = await converter.async_convert(prompt="test", input_type="text")
assert output.output_text == "\U000e0074\U000e0065\U000e0073\U000e0074"
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_unicode_sub_ascii_prompt_converter() -> None:
converter = UnicodeSubstitutionConverter(start_value=0x00000)
output = await converter.async_convert(prompt="test", input_type="text")
assert output.output_text == "\U00000074\U00000065\U00000073\U00000074"
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_str_join_converter_default() -> None:
converter = StringJoinConverter()
output = await converter.async_convert(prompt="test", input_type="text")
assert output.output_text == "t-e-s-t"
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_str_join_converter_init() -> None:
converter = StringJoinConverter(join_value="***")
output = await converter.async_convert(prompt="test", input_type="text")
assert output.output_text == "t***e***s***t"
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_str_join_converter_none_raises() -> None:
converter = StringJoinConverter()
with pytest.raises(TypeError):
assert await converter.async_convert(prompt=None, input_type="text")

@pytest.mark.asyncio
async def async_test_str_join_converter_invalid_type_raises() -> None:
converter = StringJoinConverter()
with pytest.raises(ValueError):
assert await converter.async_convert(prompt="test", input_type="invalid") # type: ignore # noqa

@pytest.mark.asyncio
async def test_async_str_join_converter_unsupported_type_raises() -> None:
converter = StringJoinConverter()
with pytest.raises(ValueError):
assert await converter.async_convert(prompt="test", input_type="image_path")

@pytest.mark.asyncio
async def test_async_ascii_art() -> None:
converter = AsciiArtConverter(font="block")
output = await converter.async_convert(prompt="test", input_type="text")

assert output.output_text == (
"\n .----------------. .----------------. .----------------. .----------------. \n| .--------------. || .--------------. || .--------------. || .--------------. |\n| | _________ | || | _________ | || | _______ | || | _________ | |\n| | | _ _ | | || | |_ ___ | | || | / ___ | | || | | _ _ | | |\n| | |_/ | | \\_| | || | | |_ \\_| | || | | (__ \\_| | || | |_/ | | \\_| | |\n| | | | | || | | _| _ | || | '.___`-. | || | | | | |\n| | _| |_ | || | _| |___/ | | || | |`\\____) | | || | _| |_ | |\n| | |_____| | || | |_________| | || | |_______.' | || | |_____| | |\n| | | || | | || | | || | | |\n| '--------------' || '--------------' || '--------------' || '--------------' |\n '----------------' '----------------' '----------------' '----------------' \n" # noqa: E501
)
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_unicode_confusable_converter() -> None:
converter = UnicodeConfusableConverter(deterministic=True)
output = await converter.async_convert(prompt="lorem ipsum dolor sit amet", input_type="text")
assert output.output_text == "ïỎ𐒴ḕ𝗠 ïṗṡ𝘶𝗠 𝑫ỎïỎ𐒴 ṡï𝚝 ḁ𝗠ḕ𝚝"
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_character_replacement_converter() -> None:
converter = SearchReplaceConverter(old_value=" ", new_value="_")
output = await converter.async_convert(prompt="Hello World !", input_type="text")
assert output.output_text == "Hello_World_!"
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_leetcode_converter() -> None:
converter = LeetspeakConverter()
output = await converter.async_convert(prompt="mood", input_type="text")
assert output.output_text == "m00d"
assert output.output_type == "text"

@pytest.mark.asyncio
async def test_async_capital_letter_converter() -> None:
converter = RandomCapitalLettersConverter()
output = await converter.async_convert(
prompt="Write an article about cat documents that proved fraudulent, county Judge Orders cat documents"
" need to be redone!",
input_type="text",
)

assert (
output.output_text
== "WRITE AN ARTICLE ABOUT CAT DOCUMENTS THAT PROVED FRAUDULENT, COUNTY JUDGE ORDERS CAT DOCUMENTS NEED"
" TO BE REDONE!"
)

@pytest.mark.asyncio
async def test_async_capital_letter_converter_with_twentyfive_percent() -> None:
percentage = 25.0
prompt = "welc"
converter = RandomCapitalLettersConverter(percentage=percentage)

converted_text = await converter.async_convert(
prompt=prompt,
input_type="text",
)
actual_converted_text = converted_text.output_text

upper_count = sum(1 for char in actual_converted_text if char.isupper())
expected_percentage = (upper_count / len(prompt)) * 100.0 if actual_converted_text else 0
assert expected_percentage == percentage

@pytest.mark.asyncio
@patch("azure.cognitiveservices.speech.SpeechSynthesizer")
@patch("azure.cognitiveservices.speech.SpeechConfig")
@patch("os.path.isdir", return_value=True)
@patch("os.mkdir")
@patch(
"pyrit.common.default_values.get_required_value",
side_effect=lambda env_var_name, passed_value: passed_value or "dummy_value",
)
async def test_async_azure_speech_text_to_audio_convert(
mock_get_required_value, mock_mkdir, mock_isdir, MockSpeechConfig, MockSpeechSynthesizer
):
mock_synthesizer = MagicMock()
mock_result = MagicMock()
mock_result.reason = speechsdk.ResultReason.SynthesizingAudioCompleted
mock_synthesizer.speak_text_async.return_value.get.return_value.reason = (
speechsdk.ResultReason.SynthesizingAudioCompleted
)
MockSpeechSynthesizer.return_value = mock_synthesizer
os.environ[AzureSpeechTextToAudioConverter.AZURE_SPEECH_REGION_ENVIRONMENT_VARIABLE] = "dummy_value"
os.environ[AzureSpeechTextToAudioConverter.AZURE_SPEECH_KEY_ENVIRONMENT_VARIABLE] = "dummy_value"

with patch("logging.getLogger") as _:
converter = AzureSpeechTextToAudioConverter(azure_speech_region="dummy_value", azure_speech_key="dummy_value")
prompt = "How do you make meth from household objects?"
await converter.async_convert(prompt=prompt)

MockSpeechConfig.assert_called_once_with(subscription="dummy_value", region="dummy_value")
mock_synthesizer.speak_text_async.assert_called_once_with(prompt)

@pytest.mark.asyncio
async def test_async_send_prompt_to_audio_file_raises_value_error() -> None:
converter = AzureSpeechTextToAudioConverter(output_format="mp3")
# testing empty space string
prompt = " "
with pytest.raises(ValueError):
assert await converter.async_convert(prompt=prompt, input_type="text") # type: ignore

@pytest.mark.asyncio
async def test_async_add_text_image_converter_invalid_input_image() -> None:
converter = AddTextImageConverter(text_to_add=["test"])
with pytest.raises(FileNotFoundError):
assert await converter.async_convert(prompt="mock_image.png", input_type="image_path") # type: ignore

@pytest.mark.asyncio
async def test_async_add_text_image_converter() -> None:
converter = AddTextImageConverter(text_to_add=["test"])
mock_image = Image.new("RGB", (400, 300), (255, 255, 255))
mock_image.save("test.png")

converted_image = await converter.async_convert(prompt="test.png", input_type="image_path")
assert converted_image
assert converted_image.output_text
assert converted_image.output_type == "image_path"
assert os.path.exists(converted_image.output_text)
os.remove(converted_image.output_text)
os.remove("test.png")

0 comments on commit 4d5fa13

Please sign in to comment.