diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 0000000..c4e765c --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,10 @@ +--- +engines: + codenarc: + exclude_paths: + - "src/it/**" +exclude_paths: + - "src/it/**" + - "**/mock-loggers.css" + - "**/prism.css" + - "**/prism.js" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dbe0df8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,1220 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[*.css] +ij_css_align_closing_brace_with_properties = false +ij_css_blank_lines_around_nested_selector = 1 +ij_css_blank_lines_between_blocks = 1 +ij_css_block_comment_add_space = false +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false +ij_css_hex_color_long_format = false +ij_css_hex_color_lower_case = false +ij_css_hex_color_short_format = false +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +ij_css_keep_single_line_blocks = false +ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_css_space_after_colon = true +ij_css_space_before_opening_brace = true +ij_css_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[*.java] +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_deconstruction_list_components = true +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = false +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = false +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = false +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_align_types_in_multi_catch = true +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = normal +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = true +ij_java_binary_operation_wrap = normal +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 1 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = normal +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 999 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = false +ij_java_do_while_brace_force = always +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_entity_dd_prefix = +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_prefix = +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_prefix = +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_li_suffix = +ij_java_entity_pk_class = java.lang.String +ij_java_entity_ri_prefix = +ij_java_entity_ri_suffix = +ij_java_entity_vo_prefix = +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = off +ij_java_enum_field_annotation_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = normal +ij_java_field_annotation_wrap = split_into_lines +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_filter_class_prefix = +ij_java_filter_class_suffix = +ij_java_filter_dd_prefix = +ij_java_filter_dd_suffix = +ij_java_finally_on_new_line = false +ij_java_for_brace_force = always +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = normal +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = always +ij_java_imports_layout = $*,|,* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = true +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 1 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = false +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_listener_class_prefix = +ij_java_listener_class_suffix = +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_message_dd_prefix = +ij_java_message_dd_suffix = EJB +ij_java_message_eb_prefix = +ij_java_message_eb_suffix = Bean +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = normal +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = normal +ij_java_modifier_list_wrap = false +ij_java_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 999 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_annotations = +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = false +ij_java_servlet_class_prefix = +ij_java_servlet_class_suffix = +ij_java_servlet_dd_prefix = +ij_java_servlet_dd_suffix = +ij_java_session_dd_prefix = +ij_java_session_dd_suffix = EJB +ij_java_session_eb_prefix = +ij_java_session_eb_suffix = Bean +ij_java_session_hi_prefix = +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_li_suffix = +ij_java_session_ri_prefix = +ij_java_session_ri_suffix = +ij_java_session_si_prefix = +ij_java_session_si_suffix = Service +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_deconstruction_list = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_annotation_eq = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_deconstruction_list = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = true +ij_java_ternary_operation_wrap = normal +ij_java_test_name_prefix = +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = normal +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = always +ij_java_while_on_new_line = false +ij_java_wrap_comments = true +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false +ij_java_wrap_semicolon_after_call_chain = false + +[*.less] +tab_width = 4 +ij_continuation_indent_size = 8 +ij_less_align_closing_brace_with_properties = false +ij_less_blank_lines_around_nested_selector = 1 +ij_less_blank_lines_between_blocks = 1 +ij_less_block_comment_add_space = false +ij_less_brace_placement = 0 +ij_less_enforce_quotes_on_format = false +ij_less_hex_color_long_format = false +ij_less_hex_color_lower_case = false +ij_less_hex_color_short_format = false +ij_less_hex_color_upper_case = false +ij_less_keep_blank_lines_in_code = 2 +ij_less_keep_indents_on_empty_lines = false +ij_less_keep_single_line_blocks = false +ij_less_line_comment_add_space = false +ij_less_line_comment_at_first_column = false +ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_less_space_after_colon = true +ij_less_space_before_opening_brace = true +ij_less_use_double_quotes = true +ij_less_value_alignment = 0 + +[*.proto] +max_line_length = 80 +ij_continuation_indent_size = 2 +ij_protobuf_keep_blank_lines_in_code = 2 +ij_protobuf_keep_indents_on_empty_lines = false +ij_protobuf_keep_line_breaks = true +ij_protobuf_space_after_comma = true +ij_protobuf_space_before_comma = false +ij_protobuf_spaces_around_assignment_operators = true +ij_protobuf_spaces_within_braces = false +ij_protobuf_spaces_within_brackets = false + +[*.sass] +ij_sass_align_closing_brace_with_properties = false +ij_sass_blank_lines_around_nested_selector = 1 +ij_sass_blank_lines_between_blocks = 1 +ij_sass_brace_placement = 0 +ij_sass_enforce_quotes_on_format = false +ij_sass_hex_color_long_format = false +ij_sass_hex_color_lower_case = false +ij_sass_hex_color_short_format = false +ij_sass_hex_color_upper_case = false +ij_sass_keep_blank_lines_in_code = 2 +ij_sass_keep_indents_on_empty_lines = false +ij_sass_keep_single_line_blocks = false +ij_sass_line_comment_add_space = false +ij_sass_line_comment_at_first_column = false +ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_sass_space_after_colon = true +ij_sass_space_before_opening_brace = true +ij_sass_use_double_quotes = true +ij_sass_value_alignment = 0 + +[*.scss] +ij_scss_align_closing_brace_with_properties = false +ij_scss_blank_lines_around_nested_selector = 1 +ij_scss_blank_lines_between_blocks = 1 +ij_scss_block_comment_add_space = false +ij_scss_brace_placement = 0 +ij_scss_enforce_quotes_on_format = false +ij_scss_hex_color_long_format = false +ij_scss_hex_color_lower_case = false +ij_scss_hex_color_short_format = false +ij_scss_hex_color_upper_case = false +ij_scss_keep_blank_lines_in_code = 2 +ij_scss_keep_indents_on_empty_lines = false +ij_scss_keep_single_line_blocks = false +ij_scss_line_comment_add_space = false +ij_scss_line_comment_at_first_column = false +ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_scss_space_after_colon = true +ij_scss_space_before_opening_brace = true +ij_scss_use_double_quotes = true +ij_scss_value_alignment = 0 + +[*.vue] +ij_vue_indent_children_of_top_level = template +ij_vue_interpolation_new_line_after_start_delimiter = true +ij_vue_interpolation_new_line_before_end_delimiter = true +ij_vue_interpolation_wrap = off +ij_vue_keep_indents_on_empty_lines = false +ij_vue_spaces_within_interpolation_expressions = true + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_continuation_indent_size = 2 +ij_xml_align_attributes = false +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.ats,*.cts,*.mts,*.ts}] +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = true +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = off +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 1 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = off +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = never +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = keep +ij_typescript_enum_constants_wrap = on_every_item +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = never +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = false +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_if_brace_force = never +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = on_every_item +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = false +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 2 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = true +ij_typescript_keep_simple_blocks_in_one_line = false +ij_typescript_keep_simple_methods_in_one_line = false +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = off +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = off +ij_typescript_object_literal_wrap = on_every_item +ij_typescript_object_types_wrap = on_every_item +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = false +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_property_prefix = +ij_typescript_reformat_c_style_comments = false +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = true +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = true +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = true +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = false +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = false +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = off +ij_typescript_union_types_wrap = on_every_item +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = true +ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_import_type = auto +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = true +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = never +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = false + +[{*.bash,*.sh,*.zsh}] +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.cjs,*.js}] +max_line_length = 80 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = false +ij_javascript_align_multiline_parameters = false +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = normal +ij_javascript_assignment_wrap = off +ij_javascript_binary_operation_sign_on_next_line = true +ij_javascript_binary_operation_wrap = normal +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 1 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = normal +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = always +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = keep +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = always +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = normal +ij_javascript_force_quote_style = false +ij_javascript_force_semicolon_style = false +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_if_brace_force = always +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = on_every_item +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = false +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 1 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = false +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = off +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = normal +ij_javascript_object_literal_wrap = on_every_item +ij_javascript_object_types_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_property_prefix = +ij_javascript_reformat_c_style_comments = false +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = false +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = false +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = true +ij_javascript_ternary_operation_wrap = normal +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = true +ij_javascript_use_explicit_js_extension = auto +ij_javascript_use_import_type = auto +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = true +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = always +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false + +[{*.ft,*.vm,*.vsl}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_vtl_keep_indents_on_empty_lines = false + +[{*.gant,*.groovy,*.gy}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_add_space = false +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enable_groovydoc_formatting = true +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_ginq_general_clause_wrap_policy = 2 +ij_groovy_ginq_having_wrap_policy = 1 +ij_groovy_ginq_indent_having_clause = true +ij_groovy_ginq_indent_on_clause = true +ij_groovy_ginq_on_wrap_policy = 1 +ij_groovy_ginq_space_after_keyword = true +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_add_space_on_reformat = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_record_parentheses = false +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_chain_calls_after_dot = false +ij_groovy_wrap_long_lines = false + +[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,bowerrc,jest.config}] +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.http,*.rest}] +indent_size = 0 +ij_http-request_call_parameters_wrap = normal +ij_http-request_method_parameters_wrap = split_into_lines +ij_http-request_space_before_comma = true +ij_http-request_spaces_around_assignment_operators = true + +[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_jsp_jsp_prefer_comma_separated_import_list = false +ij_jsp_keep_indents_on_empty_lines = false + +[{*.jspx,*.tagx}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_jspx_keep_indents_on_empty_lines = false + +[{*.kt,*.kts}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_add_space = false +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_break_after_multiline_when_entry = true +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.markdown,*.md}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.pb,*.textproto,*.txtpb}] +ij_prototext_keep_blank_lines_in_code = 2 +ij_prototext_keep_indents_on_empty_lines = false +ij_prototext_keep_line_breaks = true +ij_prototext_space_after_colon = true +ij_prototext_space_after_comma = true +ij_prototext_space_before_colon = false +ij_prototext_space_before_comma = false +ij_prototext_spaces_within_braces = true +ij_prototext_spaces_within_brackets = false + +[{*.properties,spring.handlers,spring.schemas}] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[{*.qute.htm,*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_qute_keep_indents_on_empty_lines = false + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_toml_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..a4144d0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,68 @@ +name: Bug report +description: Create a report to help me improve +labels: + - bug +assignees: [ "vitalijr2" ] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + **Before opening a new issue**, make sure to search for keywords in the issues + filtered by the "bug" label: + + * https://github.com/vitalijr2/mock-loggers/issues?q=is%3Aissue+label%3Abug+ + + and verify the issue you're about to submit isn't a duplicate. + - type: input + id: summary + validations: + required: true + attributes: + label: Summary + description: A clear and concise description of what the bug is. + - type: textarea + id: what-happened + validations: + required: true + attributes: + label: What is the current bug behavior? + description: Describe what actually happens. + placeholder: Tell us what you do and what you see! + - type: textarea + id: steps + attributes: + label: Steps to reproduce + value: | + 1. ... + 2. ... + 3. ... + ... + - type: textarea + id: expected + validations: + required: true + attributes: + label: What is the expected correct behavior? + description: Describe what you should see instead. + placeholder: Tell us what you expected to get! + - type: textarea + id: misc + attributes: + label: Additional context + description: Add any other context about the problem here. + - type: textarea + id: fixes + attributes: + label: Possible fixes + description: If you can, link to the line of code that might be responsible for the problem. + - type: markdown + attributes: + value: | + Paste any relevant logs - please use code blocks (```) + to format console output, logs, and code as it's tough + to read otherwise. + + **Do not attach ZIP files** of your code or compiled projects - instead, + please publish your code to a public GitHub repo & post a link to it. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..98f2b5c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,44 @@ +name: Feature request +description: Suggest an idea for this project +labels: + - enhancement +assignees: [ "vitalijr2" ] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request! + + **Before opening a new issue**, make sure to search for keywords in the issues + filtered by the "enhancement" and "suggestion" labels: + + * https://github.com/vitalijr2/mock-loggers/issues?q=is%3Aissue+label%3Aenhancement+ + * https://github.com/vitalijr2/mock-loggers/issues?q=is%3Aissue+label%3Asuggestion+ + + and verify the issue you're about to submit isn't a duplicate. + - type: input + id: summary + validations: + required: true + attributes: + label: Summary + description: A clear and concise description of what the bug is. + - type: textarea + id: expected + validations: + required: true + attributes: + label: What is the expected correct behavior? + description: Describe what you should see instead. + placeholder: Tell us what you expected to get! + - type: textarea + id: implementation + attributes: + label: Possible implementations + description: If you can, link to the line of code that might be responsible for the feature. + - type: markdown + attributes: + value: | + Paste any relevant logs - please use code blocks (```) + to format console output, logs, and code as it's tough + to read otherwise. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b276c03 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + # Maintain dependencies for Maven + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + day: "sunday" + target-branch: "development" + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + day: "sunday" + target-branch: "development" diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml new file mode 100644 index 0000000..5e54fe9 --- /dev/null +++ b/.github/workflows/check-release.yml @@ -0,0 +1,26 @@ +name: "Check release" + +on: + push: + tags: + - v* + +permissions: read-all + +jobs: + build: + name: Maven build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Java JDK + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: 11 + - name: Build with Maven + env: + SIGN_KEY: ${{ secrets.SIGN_KEY }} + SIGN_KEY_PASS: ${{ secrets.SIGN_KEY_PASS }} + run: ./mvnw --batch-mode -s .mvn/ci_settings.xml -ntp -DskipTests -DskipPublishing -Prun-its,release \ No newline at end of file diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml new file mode 100644 index 0000000..5ecada5 --- /dev/null +++ b/.github/workflows/codacy.yml @@ -0,0 +1,73 @@ +name: "Codacy" + +on: + push: + branches: [ "**" ] + tags-ignore: + - v* + pull_request: + # The branches below must be a subset of the branches above + branches: [ "**" ] + +permissions: read-all + +jobs: + build: + name: Maven build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Java JDK + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: 11 + - name: Build with Maven + run: ./mvnw --batch-mode + - name: Temporarily save target and test requests + uses: actions/upload-artifact@master + with: + name: targets + path: | + core/target + jdk-platform-logging/targets + retention-days: 1 + codacy-security-scan: + name: Codacy Security Scan + needs: build + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Run Codacy Analysis CLI + uses: codacy/codacy-analysis-cli-action@09916000460adeeedc96b9704f86deba53e2ad5d + with: + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + verbose: true + output: results.sarif + format: sarif + gh-code-scanning-compat: true + - name: Upload SARIF results file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif + codacy-coverage-reporter: + name: Codacy Coverage Reporter + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Retrieve saved test requests and target + uses: actions/download-artifact@master + with: + name: targets + path: . + - name: Run codacy-coverage-reporter + uses: codacy/codacy-coverage-reporter-action@a38818475bb21847788496e9f0fddaa4e84955ba + with: + coverage-reports: core/target/site/jacoco/jacoco.xml,jdk-platform-logging/target/site/jacoco/jacoco.xml + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..214c5cf --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,45 @@ +name: "CodeQL" + +on: + push: + branches: [ "**" ] + tags-ignore: + - v* + pull_request: + # The branches below must be a subset of the branches above + branches: [ "**" ] + +permissions: read-all + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + security-events: write + strategy: + fail-fast: false + matrix: + include: + - language: java-kotlin + build-mode: manual + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Java JDK + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: 11 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{matrix.language}} + build-mode: ${{matrix.build-mode}} + - name: Build with Maven + run: ./mvnw --batch-mode + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/sonatype.yml b/.github/workflows/sonatype.yml new file mode 100644 index 0000000..a289e7c --- /dev/null +++ b/.github/workflows/sonatype.yml @@ -0,0 +1,28 @@ +name: "Publish artifacts" + +on: + release: + types: [ created ] + +permissions: read-all + +jobs: + publish: + name: Publish to Maven Central + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Java JDK + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: 11 + - name: Build and publish with Maven + env: + SIGN_KEY: ${{ secrets.SIGN_KEY }} + SIGN_KEY_PASS: ${{ secrets.SIGN_KEY_PASS }} + SONATYPE_TOKEN_USERNAME: ${{ secrets.SONATYPE_TOKEN_USERNAME }} + SONATYPE_TOKEN_PASSWORD: ${{ secrets.SONATYPE_TOKEN_PASSWORD }} + run: ./mvnw --batch-mode -s .mvn/ci_settings.xml -ntp -DskipTests -Prelease diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58837fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# GitHub actions +results.sarif + +### IntelliJ IDEA ### +.idea +*.iml +*.ipr +*.iws + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ + +### NetBeans ### +nb-configuration.xml +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode + +### Mac OS ### +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env diff --git a/.mvn/ci_settings.xml b/.mvn/ci_settings.xml new file mode 100644 index 0000000..26d30e8 --- /dev/null +++ b/.mvn/ci_settings.xml @@ -0,0 +1,12 @@ + + + + + central + ${env.SONATYPE_TOKEN_USERNAME} + ${env.SONATYPE_TOKEN_PASSWORD} + + + diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000..d0f3015 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1,7 @@ +--fail-at-end +--show-version +-Djava.awt.headless=true +-DinstallAtEnd=true +-DdeployAtEnd=true +-Dhttps.protocols=TLSv1.2 +-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN diff --git a/.mvn/wrapper/.gitignore b/.mvn/wrapper/.gitignore new file mode 100644 index 0000000..e72f5e8 --- /dev/null +++ b/.mvn/wrapper/.gitignore @@ -0,0 +1 @@ +maven-wrapper.jar diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..b9e9e77 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.ThreadLocalRandom; + +@SuppressWarnings("PMD.NoPackage") +public final class MavenWrapperDownloader { + private static final String WRAPPER_VERSION = "3.3.2"; + + private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); + + public static void main(String[] args) { + log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION); + + if (args.length != 2) { + System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing"); + System.exit(1); + } + + try { + log(" - Downloader started"); + final URL wrapperUrl = URI.create(args[0]).toURL(); + final String jarPath = args[1].replace("..", ""); // Sanitize path + final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize(); + downloadFileFromURL(wrapperUrl, wrapperJarPath); + log("Done"); + } catch (IOException e) { + System.err.println("- Error downloading: " + e.getMessage()); + if (VERBOSE) { + e.printStackTrace(); + } + System.exit(1); + } + } + + private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath) + throws IOException { + log(" - Downloading to: " + wrapperJarPath); + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + final String username = System.getenv("MVNW_USERNAME"); + final char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + Path temp = wrapperJarPath + .getParent() + .resolve(wrapperJarPath.getFileName() + "." + + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); + try (InputStream inStream = wrapperUrl.openStream()) { + Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING); + Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING); + } finally { + Files.deleteIfExists(temp); + } + log(" - Downloader complete"); + } + + private static void log(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + +} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..0514995 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=source +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..478fc81 --- /dev/null +++ b/NOTICE @@ -0,0 +1,14 @@ +mock-loggers +Copyright 2024 Vitalij Berdinskih + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..001f9d8 --- /dev/null +++ b/changelog.md @@ -0,0 +1,40 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- Rename to Mock Loggers +- Add SLF4J and Apache Commons Logging modules + +## 1.1.3 - 2024-11-09 + +- Improved Javadoc +- Use BOM for jUnit and Mockito +- Add activity badges, update other +- Update Maven plugins: invoker, javadoc, surefire and failsafe +- Custom annotation @MockLoggers + +## 1.1.2 - 2024-10-21 + +- Update jUnit Jupiter and Platform +- Update Mockito to 5.14 +- Update a bunch of Maven plugins: javadoc, surefire, failsafe, invoker, central-publishing, jetbrains annotations + +## 1.1.1 - 2024-08-20 + +- Update jUnit to 5.11 +- Improve extension's logging +- Clean the Maven publishing plugin's configuration. + +## 1.1.0 - 2024-07-19 + +- Implement jUnit extension. + +## 1.0.0 - 2024-07-15 + +- Implement a logger finder. +- Add Javadocs. diff --git a/commons-logging/pom.xml b/commons-logging/pom.xml new file mode 100644 index 0000000..95a93dc --- /dev/null +++ b/commons-logging/pom.xml @@ -0,0 +1,103 @@ + + + + mock-loggers-commons-logging + + + + maven-surefire-plugin + org.apache.maven.plugins + + + maven-failsafe-plugin + org.apache.maven.plugins + + + jacoco-maven-plugin + org.jacoco + + + + + + + annotations + org.jetbrains + provided + + + commons-logging + commons-logging + provided + 1.3.4 + + + mock-loggers-core + io.github.vitalijr2.logging + 1.0.0 + + + mockito-core + org.mockito + provided + + + + hamcrest + org.hamcrest + test + + + junit-jupiter-engine + org.junit.jupiter + provided + + + junit-jupiter-params + org.junit.jupiter + provided + + + mockito-junit-jupiter + org.mockito + provided + + + Mock loggers for Apache Commons Logging backed by Mockito. + 4.0.0 + Mock loggers for Apache Commons Logging + + mock-loggers + io.github.vitalijr2.logging + 1.0.0 + + + + + + + maven-invoker-plugin + org.apache.maven.plugins + + + + run-its + + + diff --git a/commons-logging/readme.md b/commons-logging/readme.md new file mode 100644 index 0000000..5437da8 --- /dev/null +++ b/commons-logging/readme.md @@ -0,0 +1,159 @@ +# Mock loggers for Apache Commons Logging + +[Apache Commons Logging][commons-logging] factory with mock loggers backed by [Mockito][]. + +> [!WARNING] +> This library does not support _parallel test execution_. + +[![Java Version][java-version]][jdk-download] +![Commons Logging Version][commons-logging-version] +![Mockito Version][mockito-version] +![Maven Central Last Update][maven-central-last-update] +[![Maven Central][maven-central]][maven-central-link] +[![Javadoc][javadoc]][javadoc-link] + +## How to use + +Just put a test dependency to your POM: +```xml + + mock-loggers-commons-logging + io.github.vitalijr2.logging + test + 1.0.0 + +``` + +The simplest usage example looks like this: +```java +@Test +void helloWorld() { + var helloService = new HelloService(); + + assertDoesNotThrow(helloService::sayHelloWorld); + + verify(LogFactory.getLog(helloService.getClass())).info("Hello World!"); +} +``` +See more details at [HelloServiceBasicTest.java](src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceBasicTest.java) + +> [!IMPORTANT] +> Keep in mind that all loggers are initialized only once during the test run. + +Therefore, a more complex example cleans the loggers after (or before) each test: +```java +// the static logger instance +private static Log log; + +// initialize the mock logger once +@BeforeAll +static void setUpClass() { + log = LogFactory.getLog(HelloService.class); +} + +// clean the mock logger after each test +@AfterEach +void tearDown() { + clearInvocations(log); +} + +// use the mock logger in a test +@DisplayName("Names") +@ParameterizedTest(name = "<{0}>") +@ValueSource(strings = {"John", "Jane"}) +void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLog = LogFactory.getLog(helloService.getClass()); + + verify(actualLog).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLog); +} +``` +See more details at [HelloServiceFullTest.java](src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceFullTest.java) + +To avoid manual cleaning of mock loggers you can use the [jUnit extension][junit-extension] for automation. +```java +@ExtendWith(MockLoggerExtension.class) +class HelloServiceExtensionTest { + + private static Log log; + + @BeforeAll + static void setUpClass() { + log = LogFactory.getLog(HelloService.class); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLog = LogFactory.getLog(helloService.getClass()); + + verify(actualLog).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLog); + } + +} +``` +See more details at [HelloServiceExtensionTest.java](src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceExtensionTest.java) + +Also you can use the annotation for automation. +```java +@MockLoggers +class HelloServiceAnnotationTest { + + private static Log log; + + @BeforeAll + static void setUpClass() { + log = LogFactory.getLog(HelloService.class); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLog = LogFactory.getLog(helloService.getClass()); + + verify(actualLog).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLog); + } + +} +``` +See more details at [HelloServiceAnnotationTest.java](src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceAnnotationTest.java) + +[commons-logging]: https://commons.apache.org/proper/commons-logging/ + +[Mockito]: https://site.mockito.org + +[java-version]: https://img.shields.io/static/v1?label=Java&message=11&color=blue&logoColor=E23D28 + +[jdk-download]: https://www.oracle.com/java/technologies/downloads/#java11 + +[commons-logging-version]: https://img.shields.io/static/v1?label=commons-logging&message=1.3.4&color=blue&logoColor=E23D28 + +[mockito-version]: https://img.shields.io/static/v1?label=Mockito&message=5.14.2&color=blue&logoColor=E23D28 + +[maven-central-last-update]: https://img.shields.io/maven-central/last-update/io.github.vitalijr2.logging/mock-loggers-commons-logging + +[maven-central]: https://img.shields.io/maven-central/v/io.github.vitalijr2.logging/mock-loggers-commons-logging + +[maven-central-link]: https://central.sonatype.com/artifact/io.github.vitalijr2.logging/mock-loggers-commons-logging?smo=true + +[javadoc]: https://javadoc.io/badge2/io.github.vitalijr2.logging/mock-loggers-commons-logging/javadoc.svg + +[javadoc-link]: https://javadoc.io/doc/io.github.vitalijr2.logging/mock-loggers-commons-logging + +[junit-extension]: ../core/ diff --git a/commons-logging/src/it/hello-commons-logging-world/extension-logging.properties b/commons-logging/src/it/hello-commons-logging-world/extension-logging.properties new file mode 100644 index 0000000..cd35a2c --- /dev/null +++ b/commons-logging/src/it/hello-commons-logging-world/extension-logging.properties @@ -0,0 +1,8 @@ +.level = INFO + +handlers = java.util.logging.ConsoleHandler + +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter + +io.github.vitalijr2.mock.jdk.platform.logging.MockLoggerExtension.level = FINE diff --git a/commons-logging/src/it/hello-commons-logging-world/pom.xml b/commons-logging/src/it/hello-commons-logging-world/pom.xml new file mode 100644 index 0000000..a77f291 --- /dev/null +++ b/commons-logging/src/it/hello-commons-logging-world/pom.xml @@ -0,0 +1,91 @@ + + + hello-commons-logging-world + + + + maven-compiler-plugin + + ${java.version} + ${java.version} + ${java.version} + + org.apache.maven.plugins + 3.13.0 + + + maven-surefire-plugin + + + extension-logging.properties + + + org.apache.maven.plugins + 3.5.2 + + + + + + + commons-logging + commons-logging + 1.3.4 + + + + @project.artifactId@ + @project.groupId@ + test + @project.version@ + + + hamcrest + org.hamcrest + test + 3.0 + + + junit-jupiter-api + org.junit.jupiter + test + ${junit-jupiter.version} + + + junit-jupiter-engine + org.junit.jupiter + test + ${junit-jupiter.version} + + + junit-jupiter-params + org.junit.jupiter + test + ${junit-jupiter.version} + + + mockito-core + org.mockito + test + ${mockito.version} + + + mockito-junit-jupiter + org.mockito + test + ${mockito.version} + + + Basic example + example.hello + 4.0.0 + + 11 + 5.11.3 + 5.14.2 + UTF-8 + + 1.0.0 + diff --git a/commons-logging/src/it/hello-commons-logging-world/src/main/java/example/hello/HelloService.java b/commons-logging/src/it/hello-commons-logging-world/src/main/java/example/hello/HelloService.java new file mode 100644 index 0000000..580a076 --- /dev/null +++ b/commons-logging/src/it/hello-commons-logging-world/src/main/java/example/hello/HelloService.java @@ -0,0 +1,30 @@ +package example.hello; + +import static java.util.Objects.requireNonNull; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class HelloService { + + private final Log log = LogFactory.getLog(HelloService.class); + + public String sayHelloWorld() { + return sayHello("World"); + } + + public String sayHello(String name) { + if (requireNonNull(name, "Name is missed").isBlank()) { + throw new IllegalArgumentException("Name is empty"); + } + + var greeting = "Hello " + name + "!"; + + if (log.isInfoEnabled()) { + log.info(greeting); + } + + return greeting; + } + +} diff --git a/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceAnnotationTest.java b/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceAnnotationTest.java new file mode 100644 index 0000000..9326dd7 --- /dev/null +++ b/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceAnnotationTest.java @@ -0,0 +1,47 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import io.github.vitalijr2.logging.mock.MockLoggers; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@MockLoggers +class HelloServiceAnnotationTest { + + private static Log log; + + @BeforeAll + static void setUpClass() { + log = LogFactory.getLog(HelloService.class); + } + + @BeforeEach + void setUp() { + when(log.isInfoEnabled()).thenReturn(true); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLog = LogFactory.getLog(helloService.getClass()); + + verify(actualLog).isInfoEnabled(); + verify(actualLog).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLog); + } + +} diff --git a/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceBasicTest.java b/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceBasicTest.java new file mode 100644 index 0000000..f1dd9e2 --- /dev/null +++ b/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceBasicTest.java @@ -0,0 +1,22 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.verify; + +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HelloServiceBasicTest { + + @DisplayName("Hello world") + @Test + void helloWorld() { + var helloService = new HelloService(); + + assertDoesNotThrow(helloService::sayHelloWorld); + + verify(LogFactory.getLog(helloService.getClass())).isInfoEnabled(); + } + +} diff --git a/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceExtensionTest.java b/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceExtensionTest.java new file mode 100644 index 0000000..54d3b82 --- /dev/null +++ b/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceExtensionTest.java @@ -0,0 +1,48 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import io.github.vitalijr2.logging.mock.MockLoggerExtension; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@ExtendWith(MockLoggerExtension.class) +class HelloServiceExtensionTest { + + private static Log log; + + @BeforeAll + static void setUpClass() { + log = LogFactory.getLog(HelloService.class); + } + + @BeforeEach + void setUp() { + when(log.isInfoEnabled()).thenReturn(true); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLog = LogFactory.getLog(helloService.getClass()); + + verify(actualLog).isInfoEnabled(); + verify(actualLog).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLog); + } + +} diff --git a/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceFullTest.java b/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceFullTest.java new file mode 100644 index 0000000..b6c7ace --- /dev/null +++ b/commons-logging/src/it/hello-commons-logging-world/src/test/java/example/hello/HelloServiceFullTest.java @@ -0,0 +1,69 @@ +package example.hello; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class HelloServiceFullTest { + + private static Log log; + + @BeforeAll + static void setUpClass() { + log = LogFactory.getLog(HelloService.class); + } + + @BeforeEach + void setUp() { + clearInvocations(log); + reset(log); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + when(log.isInfoEnabled()).thenReturn(true); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLog = LogFactory.getLog(helloService.getClass()); + + verify(actualLog).isInfoEnabled(); + verify(actualLog).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLog); + } + + @DisplayName("Null or empty name") + @ParameterizedTest(name = "<{0}>") + @NullAndEmptySource + @ValueSource(strings = " ") + void nullOrEmptyName(String name) { + var helloService = new HelloService(); + + var exception = assertThrows(RuntimeException.class, () -> helloService.sayHello(name)); + + verifyNoInteractions(LogFactory.getLog(helloService.getClass())); + + assertThat(exception.getMessage(), startsWith("Name is")); + } + +} diff --git a/commons-logging/src/it/settings.xml b/commons-logging/src/it/settings.xml new file mode 100644 index 0000000..12780df --- /dev/null +++ b/commons-logging/src/it/settings.xml @@ -0,0 +1,35 @@ + + + + + + true + + it-repo + + + local.central + + true + + + true + + @localRepositoryUrl@ + + + + + local.central + + true + + + true + + @localRepositoryUrl@ + + + + + diff --git a/commons-logging/src/main/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactory.java b/commons-logging/src/main/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactory.java new file mode 100644 index 0000000..c0d1673 --- /dev/null +++ b/commons-logging/src/main/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactory.java @@ -0,0 +1,121 @@ +/* + * Copyright 2024 Vitalij Berdinskih + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.vitalijr2.logging.mock.commons; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; + +import io.github.vitalijr2.logging.mock.MockLoggerCleaner; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; +import org.apache.commons.logging.LogFactory; +import org.jetbrains.annotations.VisibleForTesting; + +/** + * Uses {@link org.mockito.Mockito#mock(Class, String)} to get a mock that is adapted for {@link System.Logger}. + *

+ * Example: + *


+ *   {@literal @}Test
+ *   void helloWorld() {
+ *     var helloService = new HelloService();
+ *
+ *     assertDoesNotThrow(helloService::sayHelloWorld);
+ *
+ *     verify(LogFactory.getLog(helloService.getClass())).info("Hello World!");
+ *   }
+ * 
+ * + * @since 1.0.0 + */ +public class MockLoggerFactory extends LogFactory implements MockLoggerCleaner { + + private final Map loggers; + + /** + * Create a map-based logger finder. The finder uses a concurrent map: a logger name is a key. + */ + public MockLoggerFactory() { + this(new ConcurrentHashMap<>()); + } + + @VisibleForTesting + MockLoggerFactory(Map loggers) { + this.loggers = loggers; + subscribeToNotifications(); + } + + @Override + public List cleanAndReset() { + var processedLoggers = new ArrayList(); + + loggers.forEach((loggerName, logger) -> { + clearInvocations(logger); + reset(logger); + processedLoggers.add(loggerName); + }); + + return processedLoggers; + } + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public String[] getAttributeNames() { + return new String[0]; + } + + @Override + public Log getInstance(Class clazz) throws LogConfigurationException { + return getInstance(clazz.getName()); + } + + /** + * Returns an instance of Logger for the given name. + * + * @param name logging name + * @return mock logger + * @throws LogConfigurationException this is never thrown + */ + @Override + public Log getInstance(String name) throws LogConfigurationException { + return loggers.computeIfAbsent(name, key -> mock(Log.class, "Mock for logger " + key)); + } + + @Override + public void release() { + loggers.clear(); + } + + @Override + public void removeAttribute(String s) { + // do nothing + } + + @Override + public void setAttribute(String s, Object o) { + // do nothing + } + +} diff --git a/commons-logging/src/main/java/io/github/vitalijr2/logging/mock/commons/package-info.java b/commons-logging/src/main/java/io/github/vitalijr2/logging/mock/commons/package-info.java new file mode 100644 index 0000000..00a21b2 --- /dev/null +++ b/commons-logging/src/main/java/io/github/vitalijr2/logging/mock/commons/package-info.java @@ -0,0 +1,6 @@ +/** + * Mock loggers for Apache Commons Logging backed by Mockito. + * + * @since 1.0.0 + */ +package io.github.vitalijr2.logging.mock.commons; diff --git a/commons-logging/src/main/resources/commons-logging.properties b/commons-logging/src/main/resources/commons-logging.properties new file mode 100644 index 0000000..33c0387 --- /dev/null +++ b/commons-logging/src/main/resources/commons-logging.properties @@ -0,0 +1 @@ +org.apache.commons.logging.LogFactory=io.github.vitalijr2.logging.mock.commons.MockLoggerFactory \ No newline at end of file diff --git a/commons-logging/src/test/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactoryFastTest.java b/commons-logging/src/test/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactoryFastTest.java new file mode 100644 index 0000000..7a28ca6 --- /dev/null +++ b/commons-logging/src/test/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactoryFastTest.java @@ -0,0 +1,87 @@ +package io.github.vitalijr2.logging.mock.commons; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsEmptyIterable.emptyIterable; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.hamcrest.collection.IsMapWithSize.aMapWithSize; +import static org.hamcrest.core.Is.isA; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.hamcrest.object.HasToString.hasToString; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.mock; + +import java.util.HashMap; +import java.util.Map; +import org.apache.commons.logging.Log; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("fast") +class MockLoggerFactoryFastTest { + + @DisplayName("Create and add logger") + @Test + void createLogger() { + // given + var loggers = new HashMap(); + var loggerFactory = new MockLoggerFactory(loggers); + + // when + loggerFactory.getInstance("test"); + + // then + assertAll("Logger was created", () -> assertThat("size", loggers, aMapWithSize(1)), + () -> assertThat("entry", loggers, hasEntry(equalTo("test"), isA(Log.class))), + () -> assertThat(loggers.values().iterator().next(), hasToString("Mock for logger test"))); + } + + @DisplayName("Reuse existing logger") + @Test + void reuseExistingLogger() { + // given + var loggers = new HashMap<>(Map.of("test", mock(Log.class))); + var loggerFactory = new MockLoggerFactory(loggers); + + // when + loggerFactory.getInstance("test"); + + // then + assertAll("Logger was created", () -> assertThat("size", loggers, aMapWithSize(1)), + () -> assertThat("entry", loggers, hasEntry(equalTo("test"), isA(Log.class))), + () -> assertThat("Logger was created outside", loggers.values().iterator().next(), + hasToString(startsWith("Mock for Log, hashCode:")))); + } + + @DisplayName("Clean and reset loggers") + @Test + void cleanAndResetLoggers() { + // given + var loggers = new HashMap<>(Map.of("a", mock(Log.class), "b", mock(Log.class), "c", mock(Log.class))); + var loggerFactory = new MockLoggerFactory(loggers); + + // when + var loggerNames = loggerFactory.cleanAndReset(); + + // then + assertThat(loggerNames, contains("a", "b", "c")); + } + + @DisplayName("Clear loggers") + @Test + void clearLoggers() { + // given + var loggers = new HashMap<>(Map.of("a", mock(Log.class), "b", mock(Log.class), "c", mock(Log.class))); + var loggerFactory = new MockLoggerFactory(loggers); + + // when + assertDoesNotThrow(() -> loggerFactory.release()); + + // then + assertThat(loggers.entrySet(), emptyIterable()); + } + +} diff --git a/commons-logging/src/test/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactorySlowTest.java b/commons-logging/src/test/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactorySlowTest.java new file mode 100644 index 0000000..e341b9f --- /dev/null +++ b/commons-logging/src/test/java/io/github/vitalijr2/logging/mock/commons/MockLoggerFactorySlowTest.java @@ -0,0 +1,90 @@ +package io.github.vitalijr2.logging.mock.commons; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import io.github.vitalijr2.logging.mock.MockLoggerKeeper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +@Tag("slow") +class MockLoggerFactorySlowTest { + + private static Log log; + + @BeforeAll + static void setUpClass() { + log = LogFactory.getLog(MockLoggerFactorySlowTest.class); + } + + @AfterEach + void tearDown() { + clearInvocations(log); + } + + @DisplayName("Test") + @ParameterizedTest + @CsvFileSource(resources = "commons-logging.csv", numLinesToSkip = 1) + void test(String level, String message, int traceCount, int debugCount, int infoCount, int warningCount, + int errorCount, int fatalCount, String logName) { + // given + verifyNoInteractions(log); + + // when + switch (level) { + case "TRACE": + LogFactory.getLog(logName).trace(message); + break; + case "DEBUG": + LogFactory.getLog(logName).debug(message); + break; + case "INFO": + LogFactory.getLog(logName).info(message); + break; + case "WARNING": + LogFactory.getLog(logName).warn(message); + break; + case "ERROR": + LogFactory.getLog(logName).error(message); + break; + case "FATAL": + LogFactory.getLog(logName).fatal(message); + break; + default: + fail("Unknown level"); + } + + // then + verify(log, times(traceCount)).trace("test trace message"); + verify(log, times(debugCount)).debug("test debug message"); + verify(log, times(infoCount)).info("test info message"); + verify(log, times(warningCount)).warn("test warning message"); + verify(log, times(errorCount)).error("test error message"); + verify(log, times(fatalCount)).fatal("test fatal message"); + } + + @DisplayName("Clean and reset mock loggers") + @Test + void cleanAndResetMockLoggers() { + // when + var loggerFactory = new MockLoggerFactory(); + + loggerFactory.getInstance("test").info("test message"); + + MockLoggerKeeper.getInstance().cleanAndReset(); + + // then + verifyNoInteractions(loggerFactory.getInstance("test")); + } + +} diff --git a/commons-logging/src/test/resources/io/github/vitalijr2/logging/mock/commons/commons-logging.csv b/commons-logging/src/test/resources/io/github/vitalijr2/logging/mock/commons/commons-logging.csv new file mode 100644 index 0000000..ddde649 --- /dev/null +++ b/commons-logging/src/test/resources/io/github/vitalijr2/logging/mock/commons/commons-logging.csv @@ -0,0 +1,7 @@ +level, message,trace count,debug count,info count,warning count,error count,fatal count,log name +TRACE, test trace message,1,0,0,0,0,0,io.github.vitalijr2.logging.mock.commons.MockLoggerFactorySlowTest +DEBUG, test debug message,0,1,0,0,0,0,io.github.vitalijr2.logging.mock.commons.MockLoggerFactorySlowTest +INFO, test info message,0,0,1,0,0,0,io.github.vitalijr2.logging.mock.commons.MockLoggerFactorySlowTest +WARNING, test warning message,0,0,0,1,0,0,io.github.vitalijr2.logging.mock.commons.MockLoggerFactorySlowTest +ERROR, test error message,0,0,0,0,1,0,io.github.vitalijr2.logging.mock.commons.MockLoggerFactorySlowTest +FATAL, test fatal message,0,0,0,0,0,1,io.github.vitalijr2.logging.mock.commons.MockLoggerFactorySlowTest \ No newline at end of file diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..1cede3a --- /dev/null +++ b/contributing.md @@ -0,0 +1,25 @@ +# Contributing to Mock Loggers + +**Mock Loggers** is an open-source project and all contributions +are welcome to assist with its development and maintenance. + +## Issues (bug and feature tracker) + +Please report any bugs found, feature requests or other issues on +**Mock Loggers** [GitHub tracker][github-issues]. + +When creating a new issue, +try following [necolas's guidelines][issue-guidelines]. + +## Fork, patch and contribute code + +Feel free to fork **Mock Loggers** repository +at [GitHub][github-project] for your own use and updates. + +Contribute your fixes and new features back to the main codebase using +[GitHub pull requests][github-pull-requests]. + +[github-issues]: https://github.com/vitalijr2/mock-loggers/issues +[issue-guidelines]: http://github.com/necolas/issue-guidelines/#readme +[github-project]: https://github.com/vitalijr2/mock-loggers +[github-pull-requests]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..452fb09 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,72 @@ + + + + mock-loggers-core + + + + maven-surefire-plugin + org.apache.maven.plugins + + + maven-failsafe-plugin + org.apache.maven.plugins + + + jacoco-maven-plugin + org.jacoco + + + + + + + annotations + org.jetbrains + provided + + + + hamcrest + org.hamcrest + test + + + junit-jupiter-engine + org.junit.jupiter + provided + + + mockito-junit-jupiter + org.mockito + provided + + + The observer pattern is implemented here: cleaners subscribe to notifications from a keeper, who sends + alerts when mock loggers need to be cleaned and reset. Logger factories should implement the cleaner interface and + register themselves with the keeper. The jUnit extension manages the keeper and sends alerts before and after tests. + + 4.0.0 + Logger Keeper and jUnit Extension + + mock-loggers + io.github.vitalijr2.logging + 1.0.0 + + diff --git a/core/readme.md b/core/readme.md new file mode 100644 index 0000000..7b7144c --- /dev/null +++ b/core/readme.md @@ -0,0 +1,27 @@ +# Logger Keeper and jUnit Extension + +The observer pattern is implemented here: cleaners subscribe to notifications from a keeper, who sends +alerts when mock loggers need to be cleaned and reset. Logger factories should implement the cleaner interface and +register themselves with the keeper. The jUnit extension manages the keeper and sends alerts before and after tests. + +[![Java Version][java-version]][jdk-download] +![jUnit Version][junit-version] +![Maven Central Last Update][maven-central-last-update] +[![Maven Central][maven-central]][maven-central-link] +[![Javadoc][javadoc]][javadoc-link] + +[java-version]: https://img.shields.io/static/v1?label=Java&message=11&color=blue&logoColor=E23D28 + +[jdk-download]: https://www.oracle.com/java/technologies/downloads/#java11 + +[junit-version]: https://img.shields.io/static/v1?label=jUnit&message=5.11.3&color=blue&logo=junit5&logoColor=E23D28 + +[maven-central-last-update]: https://img.shields.io/maven-central/last-update/io.github.vitalijr2.logging/mock-loggers-core + +[maven-central]: https://img.shields.io/maven-central/v/io.github.vitalijr2.logging/mock-loggers-core + +[maven-central-link]: https://central.sonatype.com/artifact/io.github.vitalijr2.logging/mock-loggers-core?smo=true + +[javadoc]: https://javadoc.io/badge2/io.github.vitalijr2.logging/mock-loggers-core/javadoc.svg + +[javadoc-link]: https://javadoc.io/doc/io.github.vitalijr2.logging/mock-loggers-core diff --git a/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerCleaner.java b/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerCleaner.java new file mode 100644 index 0000000..d8c45f8 --- /dev/null +++ b/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerCleaner.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024 Vitalij Berdinskih + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.vitalijr2.logging.mock; + +import java.util.List; + +/** + * An observer to listen {@linkplain MockLoggerKeeper keeper} when to clean and reset mocks. Mock logger factories + * should implement this. + * + * @since 1.0.0 + */ +public interface MockLoggerCleaner { + + /** + * Receive notification to clean and reset mock loggers. + * + * @return list of logger names that were cleaned and reset + */ + List cleanAndReset(); + + /** + * Register itself with a keeper. + */ + default void subscribeToNotifications() { + MockLoggerKeeper.getInstance().addCleaner(this); + } + +} diff --git a/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerExtension.java b/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerExtension.java new file mode 100644 index 0000000..e9a29b9 --- /dev/null +++ b/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerExtension.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024 Vitalij Berdinskih + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.vitalijr2.logging.mock; + +import org.jetbrains.annotations.VisibleForTesting; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * A jUnit extension to clean and reset mock loggers. + *

+ * Clean and reset mock loggers before and after tests. + *

+ * Example: + *


+ *   {@literal @}ExtendWith(MockLoggerExtension.class)
+ *   class HelloServiceExtensionTest {
+ *
+ *     private static Logger logger;
+ *
+ *     {@literal @}BeforeAll
+ *     static void setUpClass() {
+ *         logger = System.getLogger("HelloService");
+ *     }
+ *
+ *     {@literal @}DisplayName("Names")
+ *     {@literal @}ParameterizedTest(name = "<{0}>")
+ *     {@literal @}ValueSource(strings = {"John", "Jane"})
+ *     void names(String name) {
+ *         var helloService = new HelloService();
+ *
+ *         assertDoesNotThrow(() -< helloService.sayHello(name));
+ *
+ *         var logger = System.getLogger("HelloService");
+ *
+ *         verify(logger).log(Level.INFO, "Hello " + name + "!");
+ *         verifyNoMoreInteractions(logger);
+ *     }
+ *
+ *   }
+ * 
+ * + * @since 1.0.0 + */ +public class MockLoggerExtension implements AfterEachCallback, BeforeEachCallback { + + private final Logger extensionLogger; + private final MockLoggerKeeper loggerKeeper; + + /** + * Create an extension. + */ + public MockLoggerExtension() { + this(MockLoggerKeeper.getInstance(), LoggerFactory.getLogger(MockLoggerExtension.class)); + } + + @VisibleForTesting + MockLoggerExtension(MockLoggerKeeper loggerKeeper, Logger extensionLogger) { + this.loggerKeeper = loggerKeeper; + this.extensionLogger = extensionLogger; + } + + /** + * Clean and reset mock loggers after tests. You are still able to run tasks in + * {@link org.junit.jupiter.api.AfterEach}. + * + * @param context the current extension context; never {@code null} + */ + @Override + public void afterEach(ExtensionContext context) { + var loggerNames = loggerKeeper.cleanAndReset(); + + extensionLogger.debug(() -> "Clean and reset the loggers: " + String.join(", ", loggerNames)); + } + + /** + * Clean and reset mock loggers before tests. You are still able to run tasks in + * {@link org.junit.jupiter.api.BeforeEach}. + * + * @param context the current extension context; never {@code null} + */ + @Override + public void beforeEach(ExtensionContext context) { + var loggerNames = loggerKeeper.cleanAndReset(); + + extensionLogger.debug(() -> "Clean and reset the loggers: " + String.join(", ", loggerNames)); + } + +} diff --git a/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerKeeper.java b/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerKeeper.java new file mode 100644 index 0000000..82d7bcb --- /dev/null +++ b/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggerKeeper.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024 Vitalij Berdinskih + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.vitalijr2.logging.mock; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.VisibleForTesting; + +/** + * An observable object to notify mock logger factories to clean and reset mocks. + * + * @since 1.0.0 + */ +public class MockLoggerKeeper { + + private static final MockLoggerKeeper INSTANCE = new MockLoggerKeeper(); + + private final Set cleaners; + + @VisibleForTesting + MockLoggerKeeper() { + cleaners = new CopyOnWriteArraySet<>(); + } + + @NotNull + public static MockLoggerKeeper getInstance() { + return INSTANCE; + } + + /** + * Add a cleaner to the set. + * + * @param cleaner mock logger factory who wants to get notification + */ + public void addCleaner(MockLoggerCleaner cleaner) { + cleaners.add(cleaner); + } + + /** + * Send notifications to all cleaners. + * + * @return list of logger names that were cleaned and reset + */ + public List cleanAndReset() { + var loggerNames = new ArrayList(); + + cleaners.forEach(cleaner -> loggerNames.addAll(cleaner.cleanAndReset())); + + return loggerNames; + } + +} diff --git a/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggers.java b/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggers.java new file mode 100644 index 0000000..5678f7e --- /dev/null +++ b/core/src/main/java/io/github/vitalijr2/logging/mock/MockLoggers.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024 Vitalij Berdinskih + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.vitalijr2.logging.mock; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Registers {@link MockLoggerExtension} for a test class. + * + * @since 1.0.0 + */ +@ExtendWith(MockLoggerExtension.class) +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MockLoggers { + +} diff --git a/core/src/main/java/io/github/vitalijr2/logging/mock/package-info.java b/core/src/main/java/io/github/vitalijr2/logging/mock/package-info.java new file mode 100644 index 0000000..1268e57 --- /dev/null +++ b/core/src/main/java/io/github/vitalijr2/logging/mock/package-info.java @@ -0,0 +1,6 @@ +/** + * The observer pattern is implemented here: cleaners subscribe to notifications from a keeper, who sends alerts when + * mock loggers need to be cleaned and reset. Logger factories should implement the cleaner interface and register + * themselves with the keeper. The jUnit extension manages the keeper and sends alerts before and after tests. + */ +package io.github.vitalijr2.logging.mock; \ No newline at end of file diff --git a/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerCleanerTest.java b/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerCleanerTest.java new file mode 100644 index 0000000..c17ed8b --- /dev/null +++ b/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerCleanerTest.java @@ -0,0 +1,34 @@ +package io.github.vitalijr2.logging.mock; + +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@Tag("slow") +@ExtendWith(MockitoExtension.class) +class MockLoggerCleanerTest { + + @Mock + private MockLoggerCleaner cleaner; + + @DisplayName("Cleaner subscribes to keeper's notifications") + @Test + void cleanerSubscribesToNotifications() { + // given + doCallRealMethod().when(cleaner).subscribeToNotifications(); + + // when + cleaner.subscribeToNotifications(); + MockLoggerKeeper.getInstance().cleanAndReset(); + + // then + verify(cleaner).cleanAndReset(); + } + +} diff --git a/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerExtensionFastTest.java b/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerExtensionFastTest.java new file mode 100644 index 0000000..dca6ed5 --- /dev/null +++ b/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerExtensionFastTest.java @@ -0,0 +1,79 @@ +package io.github.vitalijr2.logging.mock; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.function.Supplier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +@Tag("fast") +class MockLoggerExtensionFastTest { + + @Captor + private ArgumentCaptor> messageCaptor; + @Mock + private MockLoggerKeeper loggerKeeper; + @Mock + private ExtensionContext extensionContext; + @Mock + private org.junit.platform.commons.logging.Logger extensionLogger; + + + private MockLoggerExtension extension; + + @BeforeEach + void setUp() { + extension = new MockLoggerExtension(loggerKeeper, extensionLogger); + } + + @DisplayName("Clean and reset loggers after each test") + @Test + void resetLoggersAfterEachTest() { + // given + when(loggerKeeper.cleanAndReset()).thenReturn(List.of("a", "b", "c")); + + // when + assertDoesNotThrow(() -> extension.afterEach(extensionContext)); + + // then + verifyNoInteractions(extensionContext); + verify(extensionLogger).debug(messageCaptor.capture()); + verify(loggerKeeper).cleanAndReset(); + + assertEquals("Clean and reset the loggers: a, b, c", messageCaptor.getValue().get(), + "logging message"); + } + + @DisplayName("Clean and reset loggers before each test") + @Test + void resetLoggersBeforeEachTest() { + // given + when(loggerKeeper.cleanAndReset()).thenReturn(List.of("a", "b", "c")); + + // when + assertDoesNotThrow(() -> extension.beforeEach(extensionContext)); + + // then + verifyNoInteractions(extensionContext); + verify(extensionLogger).debug(messageCaptor.capture()); + verify(loggerKeeper).cleanAndReset(); + + assertEquals("Clean and reset the loggers: a, b, c", messageCaptor.getValue().get(), + "logging message"); + } + +} diff --git a/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerExtensionSlowTest.java b/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerExtensionSlowTest.java new file mode 100644 index 0000000..4cb43bc --- /dev/null +++ b/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerExtensionSlowTest.java @@ -0,0 +1,22 @@ +package io.github.vitalijr2.logging.mock; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +@Tag("slow") +class MockLoggerExtensionSlowTest { + + @DisplayName("Initialize a logger keeper on \"before all\" step") + @Test + void initLoggerFinderOnBeforeAll() { + // when and then + assertDoesNotThrow(() -> new MockLoggerExtension()); + } + +} diff --git a/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerKeeperTest.java b/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerKeeperTest.java new file mode 100644 index 0000000..4a6dd48 --- /dev/null +++ b/core/src/test/java/io/github/vitalijr2/logging/mock/MockLoggerKeeperTest.java @@ -0,0 +1,61 @@ +package io.github.vitalijr2.logging.mock; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@Tag("fast") +@ExtendWith(MockitoExtension.class) +class MockLoggerKeeperTest { + + @Mock + private MockLoggerCleaner first; + @Mock + private MockLoggerCleaner second; + + @DisplayName("Instance") + @Test + void instance() { + // when and them + assertNotNull(MockLoggerKeeper.getInstance()); + } + + @DisplayName("Happy path") + @Test + void happyPath() { + // given + var keeper = new MockLoggerKeeper(); + + when(first.cleanAndReset()).thenReturn(List.of("a", "b", "c")); + when(second.cleanAndReset()).thenReturn(List.of("x", "y", "z")); + + // when + var loggerNames = assertDoesNotThrow(() -> { + keeper.addCleaner(first); + keeper.addCleaner(second); + + return keeper.cleanAndReset(); + }); + + // then + verify(first).cleanAndReset(); + verify(second).cleanAndReset(); + verifyNoMoreInteractions(first); + verifyNoMoreInteractions(second); + + assertThat(loggerNames, contains("a", "b", "c", "x", "y", "z")); + } + +} diff --git a/jdk-platform-logging/pom.xml b/jdk-platform-logging/pom.xml new file mode 100644 index 0000000..b3fc10d --- /dev/null +++ b/jdk-platform-logging/pom.xml @@ -0,0 +1,98 @@ + + + + mock-loggers-jdk-platform-logging + + + + maven-surefire-plugin + org.apache.maven.plugins + + + maven-failsafe-plugin + org.apache.maven.plugins + + + jacoco-maven-plugin + org.jacoco + + + + + + + annotations + org.jetbrains + provided + + + mock-loggers-core + io.github.vitalijr2.logging + 1.0.0 + + + mockito-core + org.mockito + provided + + + + hamcrest + org.hamcrest + test + + + junit-jupiter-engine + org.junit.jupiter + provided + + + junit-jupiter-params + org.junit.jupiter + provided + + + mockito-junit-jupiter + org.mockito + provided + + + Mock loggers for JDK Platform Logging backed by Mockito. + 4.0.0 + Mock loggers for JDK Platform Logging + + mock-loggers + io.github.vitalijr2.logging + ../pom.xml + 1.0.0 + + + + + + + maven-invoker-plugin + org.apache.maven.plugins + + + + run-its + + + diff --git a/jdk-platform-logging/readme.md b/jdk-platform-logging/readme.md new file mode 100644 index 0000000..8c58d13 --- /dev/null +++ b/jdk-platform-logging/readme.md @@ -0,0 +1,156 @@ +# Mock loggers for JDK Platform Logging + +[JDK Platform Logging][jdk-logging] service with mock loggers backed by [Mockito][]. + +> [!WARNING] +> This library does not support _parallel test execution_. + +[![Java Version][java-version]][jdk-download] +![Mockito Version][mockito-version] +![Maven Central Last Update][maven-central-last-update] +[![Maven Central][maven-central]][maven-central-link] +[![Javadoc][javadoc]][javadoc-link] + +## How to use + +Just put a test dependency to your POM: +```xml + + mock-loggers-jdk-platform-logging + io.github.vitalijr2.logging + test + 1.0.0 + +``` + +The simplest usage example looks like this: +```java +@Test +void helloWorld() { + var helloService = new HelloService(); + + assertDoesNotThrow(helloService::sayHelloWorld); + + verify(System.getLogger("HelloService")).log(Level.INFO, "Hello World!"); +} +``` +See more details at [HelloServiceBasicTest.java](src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceBasicTest.java) + +> [!IMPORTANT] +> Keep in mind that all loggers are initialized only once during the test run. + +Therefore, a more complex example cleans the loggers after (or before) each test: +```java +// the static logger instance +private static Logger logger; + +// initialize the mock logger once +@BeforeAll +static void setUpClass() { + logger = System.getLogger("HelloService"); +} + +// clean the mock logger after each test +@AfterEach +void tearDown() { + clearInvocations(logger); +} + +// use the mock logger in a test +@DisplayName("Names") +@ParameterizedTest(name = "<{0}>") +@ValueSource(strings = {"John", "Jane"}) +void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var logger = System.getLogger("HelloService"); + + verify(logger).log(Level.INFO, "Hello " + name + "!"); + verifyNoMoreInteractions(logger); +} +``` +See more details at [HelloServiceFullTest.java](src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceFullTest.java) + +To avoid manual cleaning of mock loggers you can use the [jUnit extension][junit-extension] for automation. +```java +@ExtendWith(MockLoggerExtension.class) +class HelloServiceExtensionTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = System.getLogger("HelloService"); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var logger = System.getLogger("HelloService"); + + verify(logger).log(Level.INFO, "Hello " + name + "!"); + verifyNoMoreInteractions(logger); + } + +} +``` +See more details at [HelloServiceExtensionTest.java](src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceExtensionTest.java) + +Also you can use the annotation for automation. +```java +@MockLoggers +class HelloServiceAnnotationTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = System.getLogger("HelloService"); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var logger = System.getLogger("HelloService"); + + verify(logger).log(Level.INFO, "Hello " + name + "!"); + verifyNoMoreInteractions(logger); + } + +} +``` +See more details at [HelloServiceAnnotationTest.java](src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceAnnotationTest.java) + +[jdk-logging]: https://www.baeldung.com/java-9-logging-api "Java Platform Logging API" + +[Mockito]: https://site.mockito.org + +[java-version]: https://img.shields.io/static/v1?label=Java&message=11&color=blue&logoColor=E23D28 + +[jdk-download]: https://www.oracle.com/java/technologies/downloads/#java11 + +[mockito-version]: https://img.shields.io/static/v1?label=Mockito&message=5.14.2&color=blue&logoColor=E23D28 + +[maven-central-last-update]: https://img.shields.io/maven-central/last-update/io.github.vitalijr2.logging/mock-loggers-jdk-platform-logging + +[maven-central]: https://img.shields.io/maven-central/v/io.github.vitalijr2.logging/mock-loggers-jdk-platform-logging + +[maven-central-link]: https://central.sonatype.com/artifact/io.github.vitalijr2.logging/mock-loggers-jdk-platform-logging?smo=true + +[javadoc]: https://javadoc.io/badge2/io.github.vitalijr2.logging/mock-loggers-jdk-platform-logging/javadoc.svg + +[javadoc-link]: https://javadoc.io/doc/io.github.vitalijr2.logging/mock-loggers-jdk-platform-logging + +[junit-extension]: ../core/ diff --git a/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/extension-logging.properties b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/extension-logging.properties new file mode 100644 index 0000000..cd35a2c --- /dev/null +++ b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/extension-logging.properties @@ -0,0 +1,8 @@ +.level = INFO + +handlers = java.util.logging.ConsoleHandler + +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter + +io.github.vitalijr2.mock.jdk.platform.logging.MockLoggerExtension.level = FINE diff --git a/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/pom.xml b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/pom.xml new file mode 100644 index 0000000..ebcc0be --- /dev/null +++ b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/pom.xml @@ -0,0 +1,85 @@ + + + hello-jdk-platform-logging-world + + + + maven-compiler-plugin + + ${java.version} + ${java.version} + ${java.version} + + org.apache.maven.plugins + 3.13.0 + + + maven-surefire-plugin + + + extension-logging.properties + + + org.apache.maven.plugins + 3.5.2 + + + + + + + @project.artifactId@ + @project.groupId@ + test + @project.version@ + + + hamcrest + org.hamcrest + test + 3.0 + + + junit-jupiter-api + org.junit.jupiter + test + ${junit-jupiter.version} + + + junit-jupiter-engine + org.junit.jupiter + test + ${junit-jupiter.version} + + + junit-jupiter-params + org.junit.jupiter + test + ${junit-jupiter.version} + + + mockito-core + org.mockito + test + ${mockito.version} + + + mockito-junit-jupiter + org.mockito + test + ${mockito.version} + + + Basic example + example.hello + 4.0.0 + + 11 + 5.11.3 + 5.14.2 + UTF-8 + + 1.0.0 + diff --git a/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/main/java/example/hello/HelloService.java b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/main/java/example/hello/HelloService.java new file mode 100644 index 0000000..6693d81 --- /dev/null +++ b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/main/java/example/hello/HelloService.java @@ -0,0 +1,30 @@ +package example.hello; + +import static java.util.Objects.requireNonNull; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + +public class HelloService { + + private final Logger logger = System.getLogger("HelloService"); + + public String sayHelloWorld() { + return sayHello("World"); + } + + public String sayHello(String name) { + if (requireNonNull(name, "Name is missed").isBlank()) { + throw new IllegalArgumentException("Name is empty"); + } + + var greeting = "Hello " + name + "!"; + + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, greeting); + } + + return greeting; + } + +} diff --git a/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceAnnotationTest.java b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceAnnotationTest.java new file mode 100644 index 0000000..0dee262 --- /dev/null +++ b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceAnnotationTest.java @@ -0,0 +1,47 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import io.github.vitalijr2.logging.mock.MockLoggers; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@MockLoggers +class HelloServiceAnnotationTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = System.getLogger("HelloService"); + } + + @BeforeEach + void setUp() { + when(logger.isLoggable(Level.INFO)).thenReturn(true); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var logger = System.getLogger("HelloService"); + + verify(logger).isLoggable(Level.INFO); + verify(logger).log(Level.INFO, "Hello " + name + "!"); + verifyNoMoreInteractions(logger); + } + +} diff --git a/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceBasicTest.java b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceBasicTest.java new file mode 100644 index 0000000..e896fef --- /dev/null +++ b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceBasicTest.java @@ -0,0 +1,23 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.verify; + +import java.lang.System.Logger.Level; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HelloServiceBasicTest { + + @DisplayName("Hello world") + @Test + void helloWorld() { + var helloService = new HelloService(); + + assertDoesNotThrow(helloService::sayHelloWorld); + + verify(System.getLogger("HelloService")).isLoggable(isA(Level.class)); + } + +} diff --git a/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceExtensionTest.java b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceExtensionTest.java new file mode 100644 index 0000000..f2b5450 --- /dev/null +++ b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceExtensionTest.java @@ -0,0 +1,48 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import io.github.vitalijr2.logging.mock.MockLoggerExtension; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@ExtendWith(MockLoggerExtension.class) +class HelloServiceExtensionTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = System.getLogger("HelloService"); + } + + @BeforeEach + void setUp() { + when(logger.isLoggable(Level.INFO)).thenReturn(true); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var logger = System.getLogger("HelloService"); + + verify(logger).isLoggable(Level.INFO); + verify(logger).log(Level.INFO, "Hello " + name + "!"); + verifyNoMoreInteractions(logger); + } + +} diff --git a/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceFullTest.java b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceFullTest.java new file mode 100644 index 0000000..e5c99bb --- /dev/null +++ b/jdk-platform-logging/src/it/hello-jdk-platform-logging-world/src/test/java/example/hello/HelloServiceFullTest.java @@ -0,0 +1,70 @@ +package example.hello; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class HelloServiceFullTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = System.getLogger("HelloService"); + } + + @BeforeEach + void setUp() { + clearInvocations(logger); + reset(logger); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + when(logger.isLoggable(Level.INFO)).thenReturn(true); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLogger = System.getLogger("HelloService"); + + verify(actualLogger).isLoggable(Level.INFO); + verify(actualLogger).log(Level.INFO, "Hello " + name + "!"); + verifyNoMoreInteractions(actualLogger); + } + + @DisplayName("Null or empty name") + @ParameterizedTest(name = "<{0}>") + @NullAndEmptySource + @ValueSource(strings = " ") + void nullOrEmptyName(String name) { + var helloService = new HelloService(); + + var exception = assertThrows(RuntimeException.class, () -> helloService.sayHello(name)); + + verifyNoInteractions(System.getLogger("HelloService")); + + assertThat(exception.getMessage(), startsWith("Name is")); + } + + +} diff --git a/jdk-platform-logging/src/it/settings.xml b/jdk-platform-logging/src/it/settings.xml new file mode 100644 index 0000000..12780df --- /dev/null +++ b/jdk-platform-logging/src/it/settings.xml @@ -0,0 +1,35 @@ + + + + + + true + + it-repo + + + local.central + + true + + + true + + @localRepositoryUrl@ + + + + + local.central + + true + + + true + + @localRepositoryUrl@ + + + + + diff --git a/jdk-platform-logging/src/main/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinder.java b/jdk-platform-logging/src/main/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinder.java new file mode 100644 index 0000000..708246e --- /dev/null +++ b/jdk-platform-logging/src/main/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinder.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024 Vitalij Berdinskih + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.vitalijr2.logging.mock.platform; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; + +import io.github.vitalijr2.logging.mock.MockLoggerCleaner; +import java.lang.System.Logger; +import java.lang.System.LoggerFinder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.jetbrains.annotations.VisibleForTesting; + +/** + * Uses {@link org.mockito.Mockito#mock(Class, String)} to get a mock that is adapted for {@link Logger}. + *

+ * Example: + *


+ *   {@literal @}Test
+ *   void helloWorld() {
+ *     var helloService = new HelloService();
+ *
+ *     assertDoesNotThrow(helloService::sayHelloWorld);
+ *
+ *     verify(System.getLogger("HelloService")).log(Level.INFO, "Hello World!");
+ *   }
+ * 
+ * + * @since 1.0.0 + */ +public class MockLoggerFinder extends LoggerFinder implements MockLoggerCleaner { + + private final Map loggers; + + /** + * Create a map-based logger finder. The finder uses a concurrent map: a logger name is a key. + */ + public MockLoggerFinder() { + this(new ConcurrentHashMap<>()); + } + + @VisibleForTesting + MockLoggerFinder(Map loggers) { + this.loggers = loggers; + subscribeToNotifications(); + } + + @Override + public List cleanAndReset() { + var processedLoggers = new ArrayList(); + + loggers.forEach((loggerName, logger) -> { + clearInvocations(logger); + reset(logger); + processedLoggers.add(loggerName); + }); + + return processedLoggers; + } + + /** + * Returns an instance of Logger for the given name, module is ignored. + * + * @param name logging name + * @param module logging module + * @return mock logger + */ + @Override + public Logger getLogger(String name, Module module) { + return loggers.computeIfAbsent(name, key -> mock(Logger.class, "Mock for logger " + key)); + } + +} diff --git a/jdk-platform-logging/src/main/java/io/github/vitalijr2/logging/mock/platform/package-info.java b/jdk-platform-logging/src/main/java/io/github/vitalijr2/logging/mock/platform/package-info.java new file mode 100644 index 0000000..6a8debd --- /dev/null +++ b/jdk-platform-logging/src/main/java/io/github/vitalijr2/logging/mock/platform/package-info.java @@ -0,0 +1,6 @@ +/** + * Mock loggers for JDK Platform Logging backed by Mockito. + * + * @since 1.0.0 + */ +package io.github.vitalijr2.logging.mock.platform; diff --git a/jdk-platform-logging/src/main/resources/META-INF/services/java.lang.System$LoggerFinder b/jdk-platform-logging/src/main/resources/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 0000000..2098a34 --- /dev/null +++ b/jdk-platform-logging/src/main/resources/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1 @@ +io.github.vitalijr2.logging.mock.platform.MockLoggerFinder diff --git a/jdk-platform-logging/src/test/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinderFastTest.java b/jdk-platform-logging/src/test/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinderFastTest.java new file mode 100644 index 0000000..7bb6b12 --- /dev/null +++ b/jdk-platform-logging/src/test/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinderFastTest.java @@ -0,0 +1,71 @@ +package io.github.vitalijr2.logging.mock.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.hamcrest.collection.IsMapWithSize.aMapWithSize; +import static org.hamcrest.core.Is.isA; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.hamcrest.object.HasToString.hasToString; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; + +import java.lang.System.Logger; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("fast") +class MockLoggerFinderFastTest { + + @DisplayName("Create and add logger") + @Test + void createLogger() { + // given + var loggers = new HashMap(); + var loggerFinder = new MockLoggerFinder(loggers); + + // when + loggerFinder.getLogger("test", getClass().getModule()); + + // then + assertAll("Logger was created", () -> assertThat("size", loggers, aMapWithSize(1)), + () -> assertThat("entry", loggers, hasEntry(equalTo("test"), isA(Logger.class))), + () -> assertThat(loggers.values().iterator().next(), hasToString("Mock for logger test"))); + } + + @DisplayName("Reuse existing logger") + @Test + void reuseExistingLogger() { + // given + var loggers = new HashMap<>(Map.of("test", mock(Logger.class))); + var loggerFinder = new MockLoggerFinder(loggers); + + // when + loggerFinder.getLogger("test", getClass().getModule()); + + // then + assertAll("Logger was reused", () -> assertThat("size", loggers, aMapWithSize(1)), + () -> assertThat("entry", loggers, hasEntry(equalTo("test"), isA(Logger.class))), + () -> assertThat("Logger was created outside", loggers.values().iterator().next(), + hasToString(startsWith("Mock for Logger, hashCode:")))); + } + + @DisplayName("Clean and reset loggers") + @Test + void cleanAndResetLoggers() { + // given + var loggers = new HashMap<>(Map.of("a", mock(Logger.class), "b", mock(Logger.class), "c", mock(Logger.class))); + var loggerFinder = new MockLoggerFinder(loggers); + + // when + var loggerNames = loggerFinder.cleanAndReset(); + + // then + assertThat(loggerNames, contains("a", "b", "c")); + } + +} diff --git a/jdk-platform-logging/src/test/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinderSlowTest.java b/jdk-platform-logging/src/test/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinderSlowTest.java new file mode 100644 index 0000000..d10e02e --- /dev/null +++ b/jdk-platform-logging/src/test/java/io/github/vitalijr2/logging/mock/platform/MockLoggerFinderSlowTest.java @@ -0,0 +1,61 @@ +package io.github.vitalijr2.logging.mock.platform; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import io.github.vitalijr2.logging.mock.MockLoggerKeeper; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@Tag("slow") +class MockLoggerFinderSlowTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = System.getLogger("test"); + } + + @AfterEach + void tearDown() { + clearInvocations(logger); + } + + @DisplayName("Test") + @ParameterizedTest + @ValueSource(strings = {"TRACE", "DEBUG", "INFO", "WARNING", "ERROR"}) + void test(Level level) { + // given + verifyNoInteractions(logger); + + // when + System.getLogger("test").log(level, "test message"); + + // then + verify(logger).log(level, "test message"); + } + + @DisplayName("Clean and reset mock loggers") + @Test + void cleanAndResetMockLoggers() { + // when + var loggerFinder = new MockLoggerFinder(); + + loggerFinder.getLogger("test", null).log(Level.INFO, "test message"); + + MockLoggerKeeper.getInstance().cleanAndReset(); + + // then + verifyNoInteractions(loggerFinder.getLogger("test", null)); + } + +} diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..5e9618c --- /dev/null +++ b/mvnw @@ -0,0 +1,332 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ]; then + + if [ -f /usr/local/etc/mavenrc ]; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ]; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ]; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false +darwin=false +mingw=false +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)" + export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home" + export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ + && JAVA_HOME="$( + cd "$JAVA_HOME" || ( + echo "cannot cd into $JAVA_HOME." >&2 + exit 1 + ) + pwd + )" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin; then + javaHome="$(dirname "$javaExecutable")" + javaExecutable="$(cd "$javaHome" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "$javaExecutable")" + fi + javaHome="$(dirname "$javaExecutable")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$( + \unset -f command 2>/dev/null + \command -v java + )" + fi +fi + +if [ ! -x "$JAVACMD" ]; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." >&2 +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" >&2 + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$( + cd "$wdir/.." || exit 1 + pwd + ) + fi + # end of workaround + done + printf '%s' "$( + cd "$basedir" || exit 1 + pwd + )" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' <"$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1 +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in wrapperUrl) + wrapperUrl="$safeValue" + break + ;; + esac + done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in wrapperSha256Sum) + wrapperSha256Sum=$value + break + ;; + esac +done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] \ + && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..1204076 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,206 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. >&2 +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. >&2 +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. >&2 +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. >&2 +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..438f63a --- /dev/null +++ b/pom.xml @@ -0,0 +1,497 @@ + + + + mock-loggers + + clean verify + + + + central-publishing-maven-plugin + org.sonatype.central + 0.6.0 + + + flatten-maven-plugin + + ossrh + + + + + flatten + + flatten + process-resources + + + + clean + + clean + clean + + + org.codehaus.mojo + 1.6.0 + + + jacoco-maven-plugin + + + + ${project.build.directory}/jacoco-ut.exec + + + prepare-agent + + prepare-agent + + + + prepare-agent-integration + + prepare-agent-integration + + + + ${project.build.directory}/jacoco.exec + + + ${project.build.directory} + + *.exec + + + + + + merge + + merge-results + verify + + + + ${project.build.directory}/jacoco.exec + ${project.reporting.outputDirectory}/jacoco + + + report + + report + verify + + + + ${project.build.directory}/jacoco.exec + + + BUNDLE + + + COMPLEXITY + 0.72 + COVEREDRATIO + + + + + + + check + + check-coverage + verify + + + org.jacoco + 0.8.12 + + + maven-clean-plugin + org.apache.maven.plugins + 3.4.0 + + + maven-compiler-plugin + + ${java.version} + ${java.version} + ${java.version} + + org.apache.maven.plugins + 3.13.0 + + + maven-enforcer-plugin + + + + [${java.version},) + + + [3.9.0,) + + + + + + + enforce + + enforce-maven + + + + enforce + + enforce-clean + pre-clean + + + org.apache.maven.plugins + 3.5.0 + + + maven-failsafe-plugin + + 1 + + **/*Test.java + + + slow + + false + + + + + integration-test + verify + + + + org.apache.maven.plugins + 3.5.2 + + + maven-install-plugin + org.apache.maven.plugins + 3.1.3 + + + maven-invoker-plugin + + ${project.build.directory}/it + + clean + verify + + ${project.build.directory}/local-repo + + */pom.xml + + verify + src/it/settings.xml + + + + + install + run + + integration-test + + + org.apache.maven.plugins + 3.8.1 + + + maven-jar-plugin + + + + ${java.vm.version} (${java.vm.vendor}) + ${project.name} + + + + org.apache.maven.plugins + 3.4.2 + + + maven-javadoc-plugin + + + + -J-Dhttp.agent=maven-javadoc-plugin_${project.groupId}:${project.artifactId} + + + + --allow-script-in-comments + + + ]]> + true + ${java.home}/bin/javadoc + + https://javadoc.io/doc/org.mockito/mockito-core/${mockito.version}/ + https://javadoc.io/doc/org.junit.jupiter/junit-jupiter-api/${junit.version}/ + + false + public + + + ]]> + + org.apache.maven.plugins + ${javadoc-plugin.version} + + + maven-resources-plugin + + ISO-8859-1 + + org.apache.maven.plugins + 3.3.1 + + + maven-source-plugin + org.apache.maven.plugins + 3.3.1 + + + maven-surefire-plugin + + + fast + + + org.apache.maven.plugins + 3.5.2 + + + sign-maven-plugin + org.simplify4u.plugins + 1.1.0 + + + + + + GitHub Actions + https://github.com/vitalijr2/mock-loggers/actions + + + + + + annotations + org.jetbrains + 26.0.1 + + + junit-jupiter-api + org.junit.jupiter + provided + ${junit.version} + + + junit-platform-commons + org.junit.platform + provided + 1.11.3 + + + mockito-core + org.mockito + provided + ${mockito.version} + + + + hamcrest + org.hamcrest + test + 3.0 + + + junit-jupiter-engine + org.junit.jupiter + test + ${junit.version} + + + junit-jupiter-params + org.junit.jupiter + test + ${junit.version} + + + mockito-junit-jupiter + org.mockito + test + ${mockito.version} + + + + + Different logging services with mock loggers backed by Mockito. + Now this library implements JDK Platform Logging and Apache Commons Logging. + + + + vitalij_r2@outlook.com + vitalijr2 + Vitalij Berdinskih + Europe/Kyiv + https://github.com/vitalijr2 + + + io.github.vitalijr2.logging + 2024 + + GitHub Issues + https://github.com/vitalijr2/mock-loggers/issues + + + + See NOTICE for third-party licenses. + repo + Apache 2 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + 4.0.0 + + commons-logging + core + jdk-platform-logging + slf4j + + Mock Loggers + pom + + + + clean deploy + + + central-publishing-maven-plugin + + central + + true + org.sonatype.central + + + maven-javadoc-plugin + + + + jar + + attach-javadocs + + + org.apache.maven.plugins + + + maven-source-plugin + + + + jar + + attach-sources + + + org.apache.maven.plugins + + + sign-maven-plugin + + + + sign + + + + org.simplify4u.plugins + + + + release + + + + clean install + + + jacoco-maven-plugin + + true + + org.jacoco + + + maven-failsafe-plugin + + true + + org.apache.maven.plugins + + + maven-install-plugin + + false + + org.apache.maven.plugins + + + maven-surefire-plugin + + true + + org.apache.maven.plugins + + + + run-its + + + + 11 + 3.11.1 + 5.11.3 + 5.14.2 + UTF-8 + + + scm:git:https://github.com/vitalijr2/mock-loggers.git + scm:git:git@github.com:vitalijr2/mock-loggers.git + https://github.com/vitalijr2/mock-loggers + + https://github.com/vitalijr2/mock-loggers + 1.0.0 + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..25d98ac --- /dev/null +++ b/readme.md @@ -0,0 +1,119 @@ +# Mock Loggers + +Different logging services can be tested using mock loggers backed by [Mockito][]. + +> [!WARNING] +> This library does not support _parallel test execution_. + +[![Java Version][java-version]][jdk-download] +![jUnit Version][junit-version] +![Mockito Version][mockito-version] +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![GitHub master check runs][github-master-check-runs]][github-master-check-runs-link] +[![Codacy Badge][codacy-badge]][codacy-badge-link] +[![Codacy Coverage][codacy-coverage]][codacy-coverage-link] +![GitHub commit activity][github-commit-activity] +[![Today's hits][today-hits]][today-hits-link] + +## How to use + +The simplest usage example looks like this: +```java +@Test +void helloWorld() { + var helloService = new HelloService(); + + assertDoesNotThrow(helloService::sayHelloWorld); + + verify(System.getLogger("HelloService")).log(Level.INFO, "Hello World!"); +} +``` + +Now this library implements services for [JDK Platform Logging][jdk-logging], +[Apache Commons Logging][commons-logging] and [SLF4J][slf4j]. + +See more examples in the relevant modules of this project: + +- for Apache Commons Logging in [mock-loggers-commons-logging](commons-logging) +- for JDK Platform Logging in [mock-loggers-jdk-platform-logging](jdk-platform-logging) +- for SLF4J in [mock-loggers-slf4j](slf4j) + +## Other logging libraries and frameworks + +- [Apache Log4j: Unit Testing in Maven][log4j-unit-testing-in-maven] +- [Unit Test logback Using JUnit][logback-unit-test-using-junit], it's old but gold + +## Credits + +There are two projects which inspired me to make this library: + +- [s4u/slf4j-mock][slf4j-mock] +- [ocarlsen/mock-slf4j-impl][mock-slf4j-impl] + +## Contributing + +Please read [Contributing](contributing.md). + +## History + +See [Changelog](changelog.md) + +## License + +Copyright 2024 Vitalij Berdinskih + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[Apache License v2.0](LICENSE) + +[Mockito]: https://site.mockito.org + +[jdk-logging]: https://www.baeldung.com/java-9-logging-api "Java Platform Logging API" + +[commons-logging]: https://commons.apache.org/proper/commons-logging/ + +[slf4j]: https://www.slf4j.org/ + +[java-version]: https://img.shields.io/static/v1?label=Java&message=11&color=blue&logoColor=E23D28 + +[jdk-download]: https://www.oracle.com/java/technologies/downloads/#java11 + +[junit-version]: https://img.shields.io/static/v1?label=jUnit&message=5.11.3&color=blue&logo=junit5&logoColor=E23D28 + +[mockito-version]: https://img.shields.io/static/v1?label=Mockito&message=5.14.2&color=blue&logoColor=E23D28 + +[github-master-check-runs]: https://img.shields.io/github/check-runs/vitalijr2/mock-loggers/master + +[github-master-check-runs-link]: https://github.com/vitalijr2/mock-loggers/actions?query=branch%3Amaster + +[codacy-badge]: https://app.codacy.com/project/badge/Grade/3c0345d6db684e388deb3357362526c0 + +[codacy-badge-link]: https://app.codacy.com/gh/vitalijr2/mock-loggers/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade + +[codacy-coverage]: https://app.codacy.com/project/badge/Coverage/3c0345d6db684e388deb3357362526c0 + +[codacy-coverage-link]: https://app.codacy.com/gh/vitalijr2/mock-loggers/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage + +[github-commit-activity]: https://img.shields.io/github/commit-activity/y/vitalijr2/mock-loggers + +[today-hits]: https://hits.sh/github.com/vitalijr2/mock-loggers.svg?view=today-total&label=today's%20hits + +[today-hits-link]: https://hits.sh/github.com/vitalijr2/mock-loggers/ + +[log4j-unit-testing-in-maven]: https://logging.apache.org/log4j/2.3.x/manual/configuration.html#UnitTestingInMaven + +[logback-unit-test-using-junit]: https://www.iamninad.com/posts/unit-test-logback-using-junit/ + +[slf4j-mock]: https://github.com/s4u/slf4j-mock + +[mock-slf4j-impl]: https://github.com/ocarlsen/mock-slf4j-impl diff --git a/security.md b/security.md new file mode 100644 index 0000000..c4c1d09 --- /dev/null +++ b/security.md @@ -0,0 +1,9 @@ +# Security Policy + +## Supported Versions + +Only the last stable version at any given point. + +## Reporting a Vulnerability + +Vulnerabilities can be disclosed via email to Vitalij_R2@outlook.com. diff --git a/slf4j/pom.xml b/slf4j/pom.xml new file mode 100644 index 0000000..7136337 --- /dev/null +++ b/slf4j/pom.xml @@ -0,0 +1,103 @@ + + + + mock-loggers-slf4j + + + + maven-surefire-plugin + org.apache.maven.plugins + + + maven-failsafe-plugin + org.apache.maven.plugins + + + jacoco-maven-plugin + org.jacoco + + + + + + + annotations + org.jetbrains + provided + + + mock-loggers-core + io.github.vitalijr2.logging + 1.0.0 + + + mockito-core + org.mockito + provided + + + slf4j-api + org.slf4j + provided + 2.0.16 + + + + hamcrest + org.hamcrest + test + + + junit-jupiter-engine + org.junit.jupiter + provided + + + junit-jupiter-params + org.junit.jupiter + provided + + + mockito-junit-jupiter + org.mockito + provided + + + Mock loggers for SLF4J backed by Mockito. + 4.0.0 + Mock loggers for SLF4J + + mock-loggers + io.github.vitalijr2.logging + 1.0.0 + + + + + + + maven-invoker-plugin + org.apache.maven.plugins + + + + run-its + + + diff --git a/slf4j/readme.md b/slf4j/readme.md new file mode 100644 index 0000000..4f03b1b --- /dev/null +++ b/slf4j/readme.md @@ -0,0 +1,160 @@ +# Mock loggers for SLF4J + +[SLF4J][slf4j] factory with mock loggers backed by [Mockito][]. + +> [!WARNING] +> This library does not support _parallel test execution_. + +[![Java Version][java-version]][jdk-download] +![SLF4J Version][slf4j-version] +![Mockito Version][mockito-version] +![Maven Central Last Update][maven-central-last-update] +[![Maven Central][maven-central]][maven-central-link] +[![Javadoc][javadoc]][javadoc-link] + +## How to use + +Just put a test dependency to your POM: +```xml + + mock-loggers-slf4j + io.github.vitalijr2.logging + test + 1.0.0 + +``` + +The simplest usage example looks like this: +```java +@Test +void helloWorld() { + var helloService = new HelloService(); + + assertDoesNotThrow(helloService::sayHelloWorld); + + verify(LoggerFactory.getLogger(helloService.getClass())).info("Hello World!"); +} +``` +See more details at [HelloServiceBasicTest.java](src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceBasicTest.java) + +> [!IMPORTANT] +> Keep in mind that all loggers are initialized only once during the test run. + +Therefore, a more complex example cleans the loggers after (or before) each test: +```java +// the static logger instance +private static Logger logger; + +// initialize the mock logger once +@BeforeAll +static void setUpClass() { + logger = LoggerFactory.getLogger(HelloService.class); +} + +// clean the mock logger after each test +@AfterEach +void tearDown() { + clearInvocations(logger); +} + +// use the mock logger in a test +@DisplayName("Names") +@ParameterizedTest(name = "<{0}>") +@ValueSource(strings = {"John", "Jane"}) +void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLogger = LoggerFactory.getLogger(helloService.getClass()); + + verify(actualLogger).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLogger); +} +``` +See more details at [HelloServiceFullTest.java](src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceFullTest.java) + +To avoid manual cleaning of mock loggers you can use the [jUnit extension][junit-extension] for automation. + +```java +@ExtendWith(MockLoggerExtension.class) +class HelloServiceExtensionTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + log = LogFactory.getLog(HelloService.class); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLogger = LoggerFactory.getLogger(helloService.getClass()); + + verify(actualLogger).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLogger); + } + +} +``` +See more details at [HelloServiceExtensionTest.java](src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceExtensionTest.java) + +Also you can use the annotation for automation. +```java +@MockLoggers +class HelloServiceAnnotationTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = LogFactory.getLog(HelloService.class); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLogger = LogFactory.getLog(helloService.getClass()); + + verify(actualLogger).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLogger); + } + +} +``` +See more details at [HelloServiceAnnotationTest.java](src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceAnnotationTest.java) + +[slf4j]: https://www.slf4j.org/ + +[Mockito]: https://site.mockito.org + +[java-version]: https://img.shields.io/static/v1?label=Java&message=11&color=blue&logoColor=E23D28 + +[jdk-download]: https://www.oracle.com/java/technologies/downloads/#java11 + +[slf4j-version]: https://img.shields.io/static/v1?label=SLF4J&message=2.0.16&color=blue&logoColor=E23D28 + +[mockito-version]: https://img.shields.io/static/v1?label=Mockito&message=5.14.2&color=blue&logoColor=E23D28 + +[maven-central-last-update]: https://img.shields.io/maven-central/last-update/io.github.vitalijr2.logging/mock-loggers-slf4j + +[maven-central]: https://img.shields.io/maven-central/v/io.github.vitalijr2.logging/mock-loggers-slf4j + +[maven-central-link]: https://central.sonatype.com/artifact/io.github.vitalijr2.logging/mock-loggers-slf4j?smo=true + +[javadoc]: https://javadoc.io/badge2/io.github.vitalijr2.logging/mock-loggers-slf4j/javadoc.svg + +[javadoc-link]: https://javadoc.io/doc/io.github.vitalijr2.logging/mock-loggers-slf4j + +[junit-extension]: ../core/ diff --git a/slf4j/src/it/hello-slf4j-world/extension-logging.properties b/slf4j/src/it/hello-slf4j-world/extension-logging.properties new file mode 100644 index 0000000..cd35a2c --- /dev/null +++ b/slf4j/src/it/hello-slf4j-world/extension-logging.properties @@ -0,0 +1,8 @@ +.level = INFO + +handlers = java.util.logging.ConsoleHandler + +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter + +io.github.vitalijr2.mock.jdk.platform.logging.MockLoggerExtension.level = FINE diff --git a/slf4j/src/it/hello-slf4j-world/pom.xml b/slf4j/src/it/hello-slf4j-world/pom.xml new file mode 100644 index 0000000..cae231f --- /dev/null +++ b/slf4j/src/it/hello-slf4j-world/pom.xml @@ -0,0 +1,91 @@ + + + hello-slf4j-world + + + + maven-compiler-plugin + + ${java.version} + ${java.version} + ${java.version} + + org.apache.maven.plugins + 3.13.0 + + + maven-surefire-plugin + + + extension-logging.properties + + + org.apache.maven.plugins + 3.5.2 + + + + + + + slf4j-api + org.slf4j + 2.0.16 + + + + @project.artifactId@ + @project.groupId@ + test + @project.version@ + + + hamcrest + org.hamcrest + test + 3.0 + + + junit-jupiter-api + org.junit.jupiter + test + ${junit-jupiter.version} + + + junit-jupiter-engine + org.junit.jupiter + test + ${junit-jupiter.version} + + + junit-jupiter-params + org.junit.jupiter + test + ${junit-jupiter.version} + + + mockito-core + org.mockito + test + ${mockito.version} + + + mockito-junit-jupiter + org.mockito + test + ${mockito.version} + + + Basic example + example.hello + 4.0.0 + + 11 + 5.11.3 + 5.14.2 + UTF-8 + + 1.0.0 + diff --git a/slf4j/src/it/hello-slf4j-world/src/main/java/example/hello/HelloService.java b/slf4j/src/it/hello-slf4j-world/src/main/java/example/hello/HelloService.java new file mode 100644 index 0000000..8d60d28 --- /dev/null +++ b/slf4j/src/it/hello-slf4j-world/src/main/java/example/hello/HelloService.java @@ -0,0 +1,30 @@ +package example.hello; + +import static java.util.Objects.requireNonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HelloService { + + private final Logger logger = LoggerFactory.getLogger(HelloService.class); + + public String sayHelloWorld() { + return sayHello("World"); + } + + public String sayHello(String name) { + if (requireNonNull(name, "Name is missed").isBlank()) { + throw new IllegalArgumentException("Name is empty"); + } + + var greeting = "Hello " + name + "!"; + + if (logger.isInfoEnabled()) { + logger.info(greeting); + } + + return greeting; + } + +} diff --git a/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceAnnotationTest.java b/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceAnnotationTest.java new file mode 100644 index 0000000..3045bb3 --- /dev/null +++ b/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceAnnotationTest.java @@ -0,0 +1,47 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import io.github.vitalijr2.logging.mock.MockLoggers; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@MockLoggers +class HelloServiceAnnotationTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = LoggerFactory.getLogger(HelloService.class); + } + + @BeforeEach + void setUp() { + when(logger.isInfoEnabled()).thenReturn(true); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLogger = LoggerFactory.getLogger(helloService.getClass()); + + verify(actualLogger).isInfoEnabled(); + verify(actualLogger).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLogger); + } + +} diff --git a/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceBasicTest.java b/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceBasicTest.java new file mode 100644 index 0000000..9f82596 --- /dev/null +++ b/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceBasicTest.java @@ -0,0 +1,22 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +class HelloServiceBasicTest { + + @DisplayName("Hello world") + @Test + void helloWorld() { + var helloService = new HelloService(); + + assertDoesNotThrow(helloService::sayHelloWorld); + + verify(LoggerFactory.getLogger(helloService.getClass())).isInfoEnabled(); + } + +} diff --git a/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceExtensionTest.java b/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceExtensionTest.java new file mode 100644 index 0000000..e9459a4 --- /dev/null +++ b/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceExtensionTest.java @@ -0,0 +1,48 @@ +package example.hello; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import io.github.vitalijr2.logging.mock.MockLoggerExtension; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ExtendWith(MockLoggerExtension.class) +class HelloServiceExtensionTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = LoggerFactory.getLogger(HelloService.class); + } + + @BeforeEach + void setUp() { + when(logger.isInfoEnabled()).thenReturn(true); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLogger = LoggerFactory.getLogger(helloService.getClass()); + + verify(actualLogger).isInfoEnabled(); + verify(actualLogger).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLogger); + } + +} diff --git a/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceFullTest.java b/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceFullTest.java new file mode 100644 index 0000000..9226a49 --- /dev/null +++ b/slf4j/src/it/hello-slf4j-world/src/test/java/example/hello/HelloServiceFullTest.java @@ -0,0 +1,69 @@ +package example.hello; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class HelloServiceFullTest { + + private static Logger logger; + + @BeforeAll + static void setUpClass() { + logger = LoggerFactory.getLogger(HelloService.class); + } + + @BeforeEach + void setUp() { + clearInvocations(logger); + reset(logger); + } + + @DisplayName("Names") + @ParameterizedTest(name = "<{0}>") + @ValueSource(strings = {"John", "Jane"}) + void names(String name) { + var helloService = new HelloService(); + + when(logger.isInfoEnabled()).thenReturn(true); + + assertDoesNotThrow(() -> helloService.sayHello(name)); + + var actualLogger = LoggerFactory.getLogger(helloService.getClass()); + + verify(actualLogger).isInfoEnabled(); + verify(actualLogger).info("Hello " + name + "!"); + verifyNoMoreInteractions(actualLogger); + } + + @DisplayName("Null or empty name") + @ParameterizedTest(name = "<{0}>") + @NullAndEmptySource + @ValueSource(strings = " ") + void nullOrEmptyName(String name) { + var helloService = new HelloService(); + + var exception = assertThrows(RuntimeException.class, () -> helloService.sayHello(name)); + + verifyNoInteractions(LoggerFactory.getLogger(helloService.getClass())); + + assertThat(exception.getMessage(), startsWith("Name is")); + } + +} diff --git a/slf4j/src/it/settings.xml b/slf4j/src/it/settings.xml new file mode 100644 index 0000000..12780df --- /dev/null +++ b/slf4j/src/it/settings.xml @@ -0,0 +1,35 @@ + + + + + + true + + it-repo + + + local.central + + true + + + true + + @localRepositoryUrl@ + + + + + local.central + + true + + + true + + @localRepositoryUrl@ + + + + + diff --git a/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactory.java b/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactory.java new file mode 100644 index 0000000..08d9629 --- /dev/null +++ b/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactory.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 Vitalij Berdinskih + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.vitalijr2.logging.mock.slf4j; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; + +import io.github.vitalijr2.logging.mock.MockLoggerCleaner; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.jetbrains.annotations.VisibleForTesting; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; + +/** + * Uses {@link org.mockito.Mockito#mock(Class, String)} to get a mock that is adapted for {@link System.Logger}. + *

+ * Example: + *


+ *   {@literal @}Test
+ *   void helloWorld() {
+ *     var helloService = new HelloService();
+ *
+ *     assertDoesNotThrow(helloService::sayHelloWorld);
+ *
+ *     verify(LoggerFactory.getLogger("HelloService")).log(Level.INFO, "Hello World!");
+ *   }
+ * 
+ * + * @since 1.0.0 + */ +public class MockLoggerFactory implements ILoggerFactory, MockLoggerCleaner { + + private final Map loggers; + + /** + * Create a map-based logger finder. The finder uses a concurrent map: a logger name is a key. + */ + public MockLoggerFactory() { + this(new ConcurrentHashMap<>()); + } + + @VisibleForTesting + public MockLoggerFactory(Map loggers) { + this.loggers = loggers; + subscribeToNotifications(); + } + + @Override + public List cleanAndReset() { + var processedLoggers = new ArrayList(); + + loggers.forEach((loggerName, logger) -> { + clearInvocations(logger); + reset(logger); + processedLoggers.add(loggerName); + }); + + return processedLoggers; + } + + /** + * Returns an instance of Logger for the given name. + * + * @param name logging name + * @return mock logger + */ + @Override + public Logger getLogger(String name) { + return loggers.computeIfAbsent(name, key -> mock(Logger.class, "Mock for logger " + key)); + } + +} diff --git a/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerServiceProvider.java b/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerServiceProvider.java new file mode 100644 index 0000000..2af0af6 --- /dev/null +++ b/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerServiceProvider.java @@ -0,0 +1,54 @@ +package io.github.vitalijr2.logging.mock.slf4j; + +import org.slf4j.ILoggerFactory; +import org.slf4j.IMarkerFactory; +import org.slf4j.helpers.BasicMarkerFactory; +import org.slf4j.helpers.NOPMDCAdapter; +import org.slf4j.spi.MDCAdapter; +import org.slf4j.spi.SLF4JServiceProvider; + +/** + * Responsible for binding the {@link MockLoggerFactory}, the {@link BasicMarkerFactory} and the {@link NOPMDCAdapter}. + * This is used by the SLF4J API. + * + * @since 1.0.0 + */ +public class MockLoggerServiceProvider implements SLF4JServiceProvider { + + private static final ILoggerFactory loggerFactory = new MockLoggerFactory(); + private static final IMarkerFactory markerFactory = new BasicMarkerFactory(); + private static final MDCAdapter contextMapAdapter = new NOPMDCAdapter(); + + /** + * Declare the version of the SLF4J API this implementation is compiled against. The value of this field is modified + * with each major release. + */ + // to avoid constant folding by the compiler, this field must *not* be final + public static String REQUESTED_API_VERSION = "2.0.99"; // !final + + @Override + public ILoggerFactory getLoggerFactory() { + return loggerFactory; + } + + @Override + public IMarkerFactory getMarkerFactory() { + return markerFactory; + } + + @Override + public MDCAdapter getMDCAdapter() { + return contextMapAdapter; + } + + @Override + public String getRequestedApiVersion() { + return REQUESTED_API_VERSION; + } + + @Override + public void initialize() { + // initialized by static fields + } + +} diff --git a/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/package-info.java b/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/package-info.java new file mode 100644 index 0000000..2acad17 --- /dev/null +++ b/slf4j/src/main/java/io/github/vitalijr2/logging/mock/slf4j/package-info.java @@ -0,0 +1,6 @@ +/** + * Mock loggers for SLF4J backed by Mockito. + * + * @since 1.0.0 + */ +package io.github.vitalijr2.logging.mock.slf4j; diff --git a/slf4j/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/slf4j/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider new file mode 100644 index 0000000..d92fa64 --- /dev/null +++ b/slf4j/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider @@ -0,0 +1 @@ +io.github.vitalijr2.logging.mock.slf4j.MockLoggerServiceProvider \ No newline at end of file diff --git a/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactoryFastTest.java b/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactoryFastTest.java new file mode 100644 index 0000000..38ba4c8 --- /dev/null +++ b/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactoryFastTest.java @@ -0,0 +1,71 @@ +package io.github.vitalijr2.logging.mock.slf4j; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.hamcrest.collection.IsMapWithSize.aMapWithSize; +import static org.hamcrest.core.Is.isA; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.hamcrest.object.HasToString.hasToString; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; + +@Tag("fast") +class MockLoggerFactoryFastTest { + + @DisplayName("Create and add logger") + @Test + void createLogger() { + // given + var loggers = new HashMap(); + var loggerFactory = new MockLoggerFactory(loggers); + + // when + loggerFactory.getLogger("test"); + + // then + assertAll("Logger was created", () -> assertThat("size", loggers, aMapWithSize(1)), + () -> assertThat("entry", loggers, hasEntry(equalTo("test"), isA(Logger.class))), + () -> assertThat(loggers.values().iterator().next(), hasToString("Mock for logger test"))); + } + + @DisplayName("Reuse existing logger") + @Test + void reuseExistingLogger() { + // given + var loggers = new HashMap<>(Map.of("test", mock(Logger.class))); + var loggerFactory = new MockLoggerFactory(loggers); + + // when + loggerFactory.getLogger("test"); + + // then + assertAll("Logger was created", () -> assertThat("size", loggers, aMapWithSize(1)), + () -> assertThat("entry", loggers, hasEntry(equalTo("test"), isA(Logger.class))), + () -> assertThat("Logger was created outside", loggers.values().iterator().next(), + hasToString(startsWith("Mock for Logger, hashCode:")))); + } + + @DisplayName("Clean and reset loggers") + @Test + void cleanAndResetLoggers() { + // given + var loggers = new HashMap<>(Map.of("a", mock(Logger.class), "b", mock(Logger.class), "c", mock(Logger.class))); + var loggerFactory = new MockLoggerFactory(loggers); + + // when + var loggerNames = loggerFactory.cleanAndReset(); + + // then + assertThat(loggerNames, contains("a", "b", "c")); + } + +} diff --git a/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactorySlowTest.java b/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactorySlowTest.java new file mode 100644 index 0000000..583f38a --- /dev/null +++ b/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerFactorySlowTest.java @@ -0,0 +1,70 @@ +package io.github.vitalijr2.logging.mock.slf4j; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Tag("slow") +class MockLoggerFactorySlowTest { + + private static Logger logger; + + @BeforeAll + static void beforeAll() { + logger = LoggerFactory.getLogger("test"); + } + + @AfterEach + void tearDown() { + clearInvocations(logger); + } + + @DisplayName("Test") + @ParameterizedTest + @CsvFileSource(resources = "slf4j.csv", numLinesToSkip = 1) + void test(String level, String message, int traceCount, int debugCount, int infoCount, int warningCount, + int errorCount) { + // given + verifyNoInteractions(logger); + + // when + switch (level) { + case "TRACE": + LoggerFactory.getLogger("test").trace(message); + break; + case "DEBUG": + LoggerFactory.getLogger("test").debug(message); + break; + case "INFO": + LoggerFactory.getLogger("test").info(message); + break; + case "WARNING": + LoggerFactory.getLogger("test").warn(message); + break; + case "ERROR": + LoggerFactory.getLogger("test").error(message); + break; + default: + fail("Unknown level"); + } + + // then + verify(logger, times(traceCount)).trace("test trace message"); + verify(logger, times(debugCount)).debug("test debug message"); + verify(logger, times(infoCount)).info("test info message"); + verify(logger, times(warningCount)).warn("test warning message"); + verify(logger, times(errorCount)).error("test error message"); + } + +} diff --git a/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerServiceProviderTest.java b/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerServiceProviderTest.java new file mode 100644 index 0000000..b285d8f --- /dev/null +++ b/slf4j/src/test/java/io/github/vitalijr2/logging/mock/slf4j/MockLoggerServiceProviderTest.java @@ -0,0 +1,34 @@ +package io.github.vitalijr2.logging.mock.slf4j; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("fast") +class MockLoggerServiceProviderTest { + + private MockLoggerServiceProvider provider; + + @BeforeEach + void setUp() { + provider = new MockLoggerServiceProvider(); + } + + @DisplayName("Initialize") + @Test + void initialize() { + // when + provider.initialize(); + + // then + assertAll("After initialization", + () -> assertNotNull(provider.getLoggerFactory(), "logger factory"), + () -> assertNotNull(provider.getMarkerFactory(), "marker factory"), + () -> assertNotNull(provider.getMDCAdapter(), "context map adapter")); + } + +} diff --git a/slf4j/src/test/resources/io/github/vitalijr2/logging/mock/slf4j/slf4j.csv b/slf4j/src/test/resources/io/github/vitalijr2/logging/mock/slf4j/slf4j.csv new file mode 100644 index 0000000..d314b35 --- /dev/null +++ b/slf4j/src/test/resources/io/github/vitalijr2/logging/mock/slf4j/slf4j.csv @@ -0,0 +1,6 @@ +level, message,trace count,debug count,info count,warning count,error count +TRACE, test trace message,1,0,0,0,0 +DEBUG, test debug message,0,1,0,0,0 +INFO, test info message,0,0,1,0,0 +WARNING, test warning message,0,0,0,1,0 +ERROR, test error message,0,0,0,0,1 \ No newline at end of file