test_prerequisites.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import json
  2. import re
  3. import tempfile
  4. from unittest.mock import Mock, mock_open
  5. import pytest
  6. from reflex import constants
  7. from reflex.config import Config
  8. from reflex.utils.prerequisites import (
  9. CpuInfo,
  10. _update_next_config,
  11. cached_procedure,
  12. get_cpu_info,
  13. initialize_requirements_txt,
  14. )
  15. @pytest.mark.parametrize(
  16. "config, export, expected_output",
  17. [
  18. (
  19. Config(
  20. app_name="test",
  21. ),
  22. False,
  23. 'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
  24. ),
  25. (
  26. Config(
  27. app_name="test",
  28. static_page_generation_timeout=30,
  29. ),
  30. False,
  31. 'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 30};',
  32. ),
  33. (
  34. Config(
  35. app_name="test",
  36. next_compression=False,
  37. ),
  38. False,
  39. 'module.exports = {basePath: "", compress: false, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
  40. ),
  41. (
  42. Config(
  43. app_name="test",
  44. frontend_path="/test",
  45. ),
  46. False,
  47. 'module.exports = {basePath: "/test", compress: true, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
  48. ),
  49. (
  50. Config(
  51. app_name="test",
  52. frontend_path="/test",
  53. next_compression=False,
  54. ),
  55. False,
  56. 'module.exports = {basePath: "/test", compress: false, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60};',
  57. ),
  58. (
  59. Config(
  60. app_name="test",
  61. ),
  62. True,
  63. 'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true, staticPageGenerationTimeout: 60, output: "export", distDir: "_static"};',
  64. ),
  65. ],
  66. )
  67. def test_update_next_config(config, export, expected_output):
  68. output = _update_next_config(config, export=export)
  69. assert output == expected_output
  70. @pytest.mark.parametrize(
  71. ("transpile_packages", "expected_transpile_packages"),
  72. (
  73. (
  74. ["foo", "@bar/baz"],
  75. ["@bar/baz", "foo"],
  76. ),
  77. (
  78. ["foo", "@bar/baz", "foo", "@bar/baz@3.2.1"],
  79. ["@bar/baz", "foo"],
  80. ),
  81. ),
  82. )
  83. def test_transpile_packages(transpile_packages, expected_transpile_packages):
  84. output = _update_next_config(
  85. Config(app_name="test"),
  86. transpile_packages=transpile_packages,
  87. )
  88. transpile_packages_match = re.search(r"transpilePackages: (\[.*?\])", output)
  89. transpile_packages_json = transpile_packages_match.group(1) # type: ignore
  90. actual_transpile_packages = sorted(json.loads(transpile_packages_json))
  91. assert actual_transpile_packages == expected_transpile_packages
  92. def test_initialize_requirements_txt_no_op(mocker):
  93. # File exists, reflex is included, do nothing
  94. mocker.patch("pathlib.Path.exists", return_value=True)
  95. mocker.patch(
  96. "charset_normalizer.from_path",
  97. return_value=Mock(best=lambda: Mock(encoding="utf-8")),
  98. )
  99. mock_fp_touch = mocker.patch("pathlib.Path.touch")
  100. open_mock = mock_open(read_data="reflex==0.6.7")
  101. mocker.patch("pathlib.Path.open", open_mock)
  102. initialize_requirements_txt()
  103. assert open_mock.call_count == 1
  104. assert open_mock.call_args.kwargs["encoding"] == "utf-8"
  105. assert open_mock().write.call_count == 0
  106. mock_fp_touch.assert_not_called()
  107. def test_initialize_requirements_txt_missing_reflex(mocker):
  108. # File exists, reflex is not included, add reflex
  109. mocker.patch("pathlib.Path.exists", return_value=True)
  110. mocker.patch(
  111. "charset_normalizer.from_path",
  112. return_value=Mock(best=lambda: Mock(encoding="utf-8")),
  113. )
  114. open_mock = mock_open(read_data="random-package=1.2.3")
  115. mocker.patch("pathlib.Path.open", open_mock)
  116. initialize_requirements_txt()
  117. # Currently open for read, then open for append
  118. assert open_mock.call_count == 2
  119. for call_args in open_mock.call_args_list:
  120. assert call_args.kwargs["encoding"] == "utf-8"
  121. assert (
  122. open_mock().write.call_args[0][0]
  123. == f"\n{constants.RequirementsTxt.DEFAULTS_STUB}{constants.Reflex.VERSION}\n"
  124. )
  125. def test_initialize_requirements_txt_not_exist(mocker):
  126. # File does not exist, create file with reflex
  127. mocker.patch("pathlib.Path.exists", return_value=False)
  128. open_mock = mock_open()
  129. mocker.patch("pathlib.Path.open", open_mock)
  130. initialize_requirements_txt()
  131. assert open_mock.call_count == 2
  132. # By default, use utf-8 encoding
  133. for call_args in open_mock.call_args_list:
  134. assert call_args.kwargs["encoding"] == "utf-8"
  135. assert open_mock().write.call_count == 1
  136. assert (
  137. open_mock().write.call_args[0][0]
  138. == f"{constants.RequirementsTxt.DEFAULTS_STUB}{constants.Reflex.VERSION}\n"
  139. )
  140. def test_requirements_txt_cannot_detect_encoding(mocker):
  141. mocker.patch("pathlib.Path.exists", return_value=True)
  142. mock_open = mocker.patch("builtins.open")
  143. mocker.patch(
  144. "charset_normalizer.from_path",
  145. return_value=Mock(best=lambda: None),
  146. )
  147. initialize_requirements_txt()
  148. mock_open.assert_not_called()
  149. def test_requirements_txt_other_encoding(mocker):
  150. mocker.patch("pathlib.Path.exists", return_value=True)
  151. mocker.patch(
  152. "charset_normalizer.from_path",
  153. return_value=Mock(best=lambda: Mock(encoding="utf-16")),
  154. )
  155. initialize_requirements_txt()
  156. open_mock = mock_open(read_data="random-package=1.2.3")
  157. mocker.patch("pathlib.Path.open", open_mock)
  158. initialize_requirements_txt()
  159. # Currently open for read, then open for append
  160. assert open_mock.call_count == 2
  161. for call_args in open_mock.call_args_list:
  162. assert call_args.kwargs["encoding"] == "utf-16"
  163. assert (
  164. open_mock().write.call_args[0][0]
  165. == f"\n{constants.RequirementsTxt.DEFAULTS_STUB}{constants.Reflex.VERSION}\n"
  166. )
  167. def test_cached_procedure():
  168. call_count = 0
  169. @cached_procedure(tempfile.mktemp(), payload_fn=lambda: "constant")
  170. def _function_with_no_args():
  171. nonlocal call_count
  172. call_count += 1
  173. _function_with_no_args()
  174. assert call_count == 1
  175. _function_with_no_args()
  176. assert call_count == 1
  177. call_count = 0
  178. @cached_procedure(
  179. tempfile.mktemp(),
  180. payload_fn=lambda *args, **kwargs: f"{repr(args), repr(kwargs)}",
  181. )
  182. def _function_with_some_args(*args, **kwargs):
  183. nonlocal call_count
  184. call_count += 1
  185. _function_with_some_args(1, y=2)
  186. assert call_count == 1
  187. _function_with_some_args(1, y=2)
  188. assert call_count == 1
  189. _function_with_some_args(100, y=300)
  190. assert call_count == 2
  191. _function_with_some_args(100, y=300)
  192. assert call_count == 2
  193. def test_get_cpu_info():
  194. cpu_info = get_cpu_info()
  195. assert cpu_info is not None
  196. assert isinstance(cpu_info, CpuInfo)
  197. assert cpu_info.model_name is not None
  198. for attr in ("manufacturer_id", "model_name", "address_width"):
  199. value = getattr(cpu_info, attr)
  200. assert value.strip() if attr != "address_width" else value